Welcome


Let’s start from the beginning..​. Quarkus. What’s Quarkus? That’s a pretty good question, and probably a good start. If you go on the Quarkus web site, Quarkus is "A Kubernetes Native Java stack tailored for OpenJDK HotSpot & GraalVM, crafted from the best of breed Java libraries and standards". This description is rather unclear, but does a very good job at using bankable keywords, right? It’s also written: "Supersonic Subatomic Java". Still very foggy. In practice, Quarkus is an Open Source stack to write Java applications, specifically backend applications. In this lab, we are going to explain what Quarkus is, and, because the best way to understand Quarkus is to use it, build a set of microservices with it. Don’t be mistaken, Quarkus is not limited to microservices, and we are going to learn about this in the workshop.

This lab offers attendees an intro-level, hands-on session with Quarkus, from the first line of code to making services, to consuming them, and finally to assembling everything in a consistent system. But, what are we going to build? Well, it’s going to be a set of microservices (we want to be trendy):

  • using Quarkus

  • using HTTP and events (Kafka)

  • with some parts of the dark side of microservices (monitoring (Prometheus), resilience)

  • answer the ultimate question: are super heroes stronger than super villains?

This workshop is a BYOL (Bring Your Own Laptop) session, so bring your Windows, OSX, or Linux laptop. You need JDK 8+ on your machine, Apache Maven (3.6+), and Docker. On Windows, some parts may be qualified as experimental. On Mac and Windows, Docker for x is recommended instead of the Docker toolbox setup.

What you are going to learn:

  • What is Quarkus and how you can use it

  • How to build an HTTP endpoint (REST API) with Quarkus

  • How to access a database

  • How you can use Swagger and OpenAPI

  • How you test your microservice

  • How you improve the resilience of your service

  • How to build event-driven and reactive microservices with Kafka

  • How to build native executable

  • How to extend Quarkus with extensions

  • And many more…​

Ready? Here we go!

Presenting the Workshop

What Is This Workshop About?

This workshop should give you a practical introduction to Quarkus. You will first install all the needed tools to then develop an entire microservice architecture, mixing classical HTTP microservices and event-based microservices. You will finish by extending the capabilities of Quarkus and learn more about the ability to create native executables.

The idea is that you leave this workshop with a good understanding of what Quarkus is, what it is not, and how it can help you in your projects. Then, you’ll be prepared to investigate a bit more and, hopefully, contribute.

What Will You Be Developing?

In this workshop you will develop an application that allows super-heroes to fight against villains. Being a workshop about microservices, you will be developing several microservices communicating either synchronously via REST or asynchronously using Kafka:

  • Super Hero UI: an Angular application allowing you to pick up a random super-hero, a random villain and makes them fight. The Super Hero UI is exposed via Quarkus and invokes the Fight REST API

  • Hero REST API: Allows CRUD operations on Heroes which are stored in a Postgres database

  • Villain REST API: Allows CRUD operations on Villains which are stored in a Postgres database

  • Fight REST API: This REST API invokes the Hero and Villain APIs to get a random super-hero and a random villain. Each fight is stored in a Postgres database

  • Statistics: Each fight is asynchronously sent (via Kafka) to the Statistics microservice. It has a HTML + JQuery UI displaying all the statistics.

  • Promotheus polls metrics from the three microservices Fight, Hero and Villain

diag b395979d484dea28598e73247cb2606b

The main UI allows you to pick up one random Hero and Villain by clicking on "New Fighters". Then it’s just a matter of clicking on "Fight!" to get them to fight. The table at the bottom shows the list of the previous fights.

angular ui

How Does This Workshop Work?

You have this material in your hands (either electronically or printed) and you can now follow it step by step. The structure of this workshop is as follow :

  • Installing all the needed tools: in this section you will install all the tools and code to be able to develop, compile and execute our application

  • Developing with Quarkus: in this section you will develop a microservice architecture by creating several Maven projects, write some Java code, add JPA entities, JAX-RS REST endpoints, write some tests, use an Angular web application, and all that on Quarkus

  • Extending Quarkus: in this section you will create a Quarkus extension

If you already have the tools installed, skip the Installing all the needed tools section and jump to the sections Developing with Quarkus and Extending Quarkus, and start hacking some code and addons. This "à la carte" mode allows you to make the most of this 6 hours long hands on lab.

What Do You Have to Do?

This workshop should be as self explanatory as possible. So your job is to follow the instructions by yourself, do what you are supposed to do, and do not hesitate to ask for any clarification or assistance, that’s why the team is here. Oh, and be ready to have some fun!

Software Requirements

First of all, make sure you have Web browser installed on your laptop and internet connectivity. You will also need a GitHub account.

Your environment is remote and can be accessed via CodeReady Workspaces (CRW) through your local browser, you just need to sign up and configure some elements. Your environment includes also Red Hat’s OpenShift Container Platform (OCP).

The next section focuses on how to install and setup the needed software. You can skip the next section if you have already installed all the prerequisites.

Installing Software

CodeReady Workspace

CodeReady Workspaces is a collaborative Kubernetes-native development solution that delivers OpenShift workspaces and in-browser IDE for rapid cloud application development. An access to CRW will be provided.

CRW Configuration
  • Access to your CRW and sign up with the credentials provided:

    user: USERNAME
    pwd: openshift
    Luego en el formulario de registro pon lo que quieras (excepto username que tiene que ser user2), por ejemplo:
    email: user2@ocp.com
    first name: User
    last name: U2
  • Configure a Quarkus workspace in the Get Started

crw getting started
  • Give more memory to your workspace: We need to increase the memory allowed to our workspace. To do that go to your workspace settings, select Devfile and modify the memoryLimit parameter, 2048Mi should be enough:

Workspaces → quarkus-XXXX → Devfile

crw set ws memory limit
memoryLimit: 2048Mi
  • Finally, open a terminal from the Terminal menu → Open Terminal in specific container → maven.

Command Line Utilities

Just make sure the following commands work on your CRW terminal

$ java -version
$ mvn -version
$ curl --version

OpenShift Container Platform

Your lab environment includes Red Hat’s OpenShift Container Platform (OCP).

Access to your OCP resources can be gained via both the oc CLI utility and the OCP web console.

Overview

The project we are going to develop will contain 3 microservices accessing to a PostgreSQL database. We will make the databases to run in a OpenShift dedicated namespace.

OpenShift Namespaces
  1. In the terminal of your CRW, authenticate into OpenShift as a non cluster admin user (USERNAME) using the oc utility.

You can get the command for authenticating from the OpenShift Web Console.
$ oc login

The namespace created for hosting your CRW environment is USERNAME-codeready where USERNAME correspond to your specific username.

  1. Create the namespace for hosting the databases running the following command.

$ oc new-project USERNAME-heroes
change the USERNAME with your own.
Operators

Your lab environment comes pre-installed with various OpenShift operators.

AMQ Streams

This operator is responsible for managing a Kafka cluster running within an OpenShift cluster. In particular, the operator will create the kafka server, the topic and configure its parameters.

You can create the server and topic that will be used during the workshop with the oc utility. The OpenShift manifests containing the resources to create can be found under quarkus-super-heroes-workshop/super-heroes/infrastructure/ directory.

From the CRW terminal where you are authenticated on OpenShift, run:

$ oc apply -f quarkus-super-heroes-workshop/super-heroes/infrastructure/kafka.yaml

PostgreSQL operator

The PostgreSQL operator allows to package, install, configure and manage a PostgreSQL database within an OpenShift cluster. This operator is not pre-installed. We need to do it.

  1. Access to your OCP Web Console and authenticate using the following user credentials

    1. username: opentlc-mgr

    2. password: r3dh4t1!

  2. In the left menu, go to Operators → OperatorHub and serch for a Postgres operator. Select the PostgreSQL Operator by Dev4Ddevs.com

  3. Click Install

  4. In the installation dialog, select the namespace USERNAME-heroes created previously in the Installed Namespace field.

  5. Click Install.

Congratulations! Your lab environment is now ready to use.

Preparing for the Workshop

This workshop needs internet access to download all sorts of Maven artifacts, Docker images and even pictures. Some of these artifacts are large, and because we have to share internet connexions at the workshop, it is better to download them prior to the workshop. Here are a few commands that you can execute before the workshop.

Get the workshop scaffolding

Call to action

First, get the workshop scaffolding by cloning the https://github.com/bbvahackathon/quarkus-super-heroes-workshop repository.

On your CRW terminal:

$ git clone https://github.com/bbvahackathon/quarkus-super-heroes-workshop

In this workshop you will be developing an application dealing with Super Heroes (and Super Villains 🦹) as well as Quarkus extensions. The code will be separated into two different directories:

diag 8eb2a78bc4d8a6913316ebafc191a3c5
Super Heroes Application

Under the super-heroes directory you will find the entire Super Hero application spread throughout a set of subdirectories, each one containing a microservice or some tooling. The final structure will be the following:

diag f22240ee8516ad821aa668c5884d50f1

Most of theses subdirectories are Maven projects and follow the Maven directory structure:

diag 1dabe787c4e2d35470a3d8a47dab85f7
Quarkus Extensions

Under the extensions directory you will find quarkus extensions. By the end of the workshop, you will get:

diag 3dcf32ea38ab6128e85c6cc871bab98a

Databases

We need to create the 3 databases that we are using. They are described in an OpenShift manifest that can be found under quarkus-super-heroes-workshop/super-heroes/infrastructure/ directory.

As we did for kafka, we will create the DBs using oc utility from the CRW terminal. Make sure that you are authenticated with your USERNAME and you are using the USERNAME-heroes namespace, then apply the databases yaml file:

$ oc login ....
$ oc project USERNAME-heroes
$ oc apply -f quarkus-super-heroes-workshop/super-heroes/infrastructure/databases.yaml

You can verify the availability of databases with the following command:

$ curl http://heroes-database.USERNAME-heroes:5432

You should have the following response:

curl: (52) Empty reply from server

Warming up Maven

Now that you have the initial structure in place, we just need configure the correct url for the database.

On your CRW, edit the quarkus-super-heroes-workshop/super-heroes/rest-villain/src/main/resources/application.properties file and the change database configuration:

quarkus.datasource.jdbc.url=jdbc:postgresql://villains-database.USERNAME-heroes:5432/villains_database

Change USERNAME with your own.

Now, you can navigate to the root directory and run:

Call to action

mvn clean install

By running this command, it downloads all the required dependencies.

What’s this infra?

Any microservice system is going to rely on a set of technical services. In our context, we are going to use PostgreSQL as the database, Prometheus as the monitoring tool, and Kafka as the event/message bus. The infrastructure folder contains the OpenShift manifests to create the OpenShift resources in order to have this techincal service available.

Ready?

Prerequisites has been installed, the different components have been warmed up, it’s now time to write some code!

Creating a REST/HTTP Microservice


At the heart of the Super Hero application comes Heroes. We need to expose a REST API allowing CRUD operations on Super Heroes. This microservice is, let’s say, a classical microservice. It uses HTTP to expose a REST API and internally store data into a database. This service will be used by the fight microservice.

diag a3075340560c850e6f3761fb15fc8b0a

In the following sections, you learn:

  • how to create a new Quarkus application

  • how to implement REST API using JAX-RS

  • how to compose your application using CDI beans

  • how to access your database using Hibernate with Panache

  • how to use transactions

  • how to enable OpenAPI and Swagger-UI

But first, let’s describe our service. The Super Heroes microservice stores super-heroes, with their names, powers, and so on. The REST API allows adding, removing, listing, and picking a random hero from the stored set. Nothing outstanding but a good first step to discover Quarkus.

Hero Microservice

First thing first, we need a project. That’s what your are going to see in this section.

Bootstrapping the Hero REST Endpoint

The easiest way to create a new Quarkus project is to use the Quarkus Maven plugin. We have created the project structure earlier, so we will move to the rest-hero directory and run the project creation command. Open a terminal and run the following command:

Call to action

Unresolved directive in rest-bootstrapping.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/bootstrap.sh[tag=adocSnippet]
Preferring Web UI

Instead of the Maven command, you can use https://code.quarkus.io.

Directory Structure

Once you bootstrap the project, you get the following directory structure with a few Java classes and other artifacts :

diag 7cc723c00e423265dc0b855d5ec8f38b

The Maven archetype generates the following rest-hero sub-directory:

  • the Maven structure with a pom.xml

  • an io.quarkus.workshop.superheroes.hero.HeroResource resource exposed on /api/heroes

  • an associated unit test HeroResourceTest

  • the landing page index.html that is accessible on http://localhost:8080 after starting the application

  • example Dockerfile files for both native and jvm modes in src/main/docker

  • the application.properties configuration file

Once generated, look at the pom.xml. You will find the import of the Quarkus BOM, allowing you to omit the version on the different Quarkus dependencies. In addition, you can see the quarkus-maven-plugin responsible of the packaging of the application and also providing the development mode.

Unresolved directive in rest-bootstrapping.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocProperties]
Unresolved directive in rest-bootstrapping.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyManagement]
Unresolved directive in rest-bootstrapping.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocBuild]

If we focus on the dependencies section, you can see the extension allowing the development of REST applications:

Unresolved directive in rest-bootstrapping.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyResteasy]
RESTEasy

You may not be familiar with RESTEasy.[1] It’s an implementation of JAX-RS and it uses to implement RestFul services in Quarkus.

What’s this Quarkus Universe thingy?

The Quarkus Universe includes Quarkus as well as third-party extensions, like Apache Camel.

The JAX-RS Resource

During the project creation, the HeroResource.java file has been created with the following content:

package io.quarkus.workshop.superheroes.hero;

@Path("/api/heroes")
public class HeroResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}

It’s a very simple REST endpoint, returning "hello" to requests on /api/heroes.

Running the Application

Now we are ready to run our application.

Call to action

Use: ./mvnw quarkus:dev:

$ ./mvnw quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] -------------< io.quarkus.workshop.super-heroes:rest-hero >-------------
[INFO] Building rest-hero 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:1.2.0.Final:dev (default-cli) @ rest-hero ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/clement/Downloads/quarkus-super-heroes-workshop/super-heroes/rest-hero/target/classes
Listening for transport dt_socket at address: 5005
2020-01-28 15:09:02,111 INFO  [io.quarkus] (main) rest-hero 1.0-SNAPSHOT (running on Quarkus 1.2.0.Final) started in 2.161s. Listening on: http://0.0.0.0:8080
2020-01-28 15:09:02,123 INFO  [io.quarkus] (main) Profile dev activated. Live Coding activated.
2020-01-28 15:09:02,124 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

Then check that the endpoint returns hello as expected:

[source,shell]Now we are ready to run our application

$ curl http://localhost:8080/api/heroes
hello

Alternatively, you can open http://localhost:8080/api/heroes in your browser.

Development Mode

quarkus:dev runs Quarkus in development mode. This enables hot deployment with background compilation, which means that when you modify your Java files and/or your resource files and invoke a REST endpoint (i.e. cUrl command or refresh your browser), these changes will automatically take effect. This works too for resource files like the configuration property and HTML files. Refreshing the browser triggers a scan of the workspace, and if any changes are detected, the Java files are recompiled and the application is redeployed; your request is then serviced by the redeployed application. If there are any issues with compilation or deployment an error page will let you know.

The development mode also allows debugging and listens for a debugger on port 5005. If you want to wait for the debugger to attach before running you can pass -Dsuspend=true on the command line. If you don’t want the debugger at all you can use -Ddebug=false.

Alright, time to change some code. Open your favorite IDE and import the project. To check that the hot reload is working, update the method HeroResource.hello() by returning the String "hello hero". Now, execute the cUrl command again, the output has changed without you to having to stop and restart Quarkus:

Call to action

$ curl http://localhost:8080/api/heroes
hello hero

Testing the Application

All right, so far so good, but wouldn’t it be better with a few tests, just in case.

In the generated pom.xml file, you can see 2 test dependencies:

Unresolved directive in rest-bootstrapping.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyTest]

Quarkus supports Junit 4 and Junit 5 tests. In the generated project, we use Junit 5. Because of this, the version of the Surefire Maven Plugin must be set, as the default version does not support Junit 5:

Unresolved directive in rest-bootstrapping.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocBuildSurefire]

We also set the java.util.logging system property to make sure tests will use the correct log manager.

The generated project contains a simple test in HeroResourceTest.java.

package io.quarkus.workshop.superheroes.hero;

@QuarkusTest
public class HeroResourceTest {

    @Test
    public void testHelloEndpoint() {
        given()
          .when().get("/api/heroes")
          .then()
             .statusCode(200)
             .body(is("hello"));
    }
}

By using the QuarkusTest runner, the HeroResourceTest class instructs JUnit to start the application before the tests. Then, the testHelloEndpoint method checks the HTTP response status code and content. Notice that these tests use RestAssured, but feel free to use your favorite library.[2]

Call to action

Execute it with ./mvnw test or from your IDE. It fails! It’s expected, you changed the output of HeroResource.hello() earlier. Adjust the test body condition accordingly.

Packaging and Running the Application

Call to action

The application is packaged using ./mvnw package. It produces 2 jar files in /target:

  • rest-hero-1.0-SNAPSHOT.jar : containing just the classes and resources of the projects, it’s the regular artifact produced by the Maven build;

  • rest-hero-1.0-SNAPSHOT-runner.jar : being an executable jar. Be aware that it’s not an über-jar as the dependencies are copied into the target/lib directory.

You can run the application using: java -jar target/rest-hero-1.0-SNAPSHOT-runner.jar.

Before running the application, don’t forget to stop the hot reload mode (hit CTRL+C), or you will have a port conflict.

Troubleshooting

You might come across the following error while developing:

WARN  [io.qu.ne.ru.NettyRecorder] (Thread-48) Localhost lookup took more than one second, you need to add a /etc/hosts entry to improve Quarkus startup time. See https://thoeni.io/post/macos-sierra-java/ for details.

If this is the case, it’s just a matter to add the node name of your machine to the /etc/hosts. For that, first get the name of your node with the following command:

$ uname -n
my-node.local

Then sudo vi /etc/hosts so you have the rights to edit the file and add the following entry

127.0.0.1 localhost my-node.local

Transactions and ORM

The Hero API’s role is to allow CRUD operations on Super Heroes. In this module we will create a Hero entity and persist/update/delete/retrieve it from a Postgres database in a transactional way.

Directory Structure

In this module we will add extra classes to the Hero API project. You will end-up with the following directory structure:

diag 3cb77e14182ee70b57a6f942cf15bc26

Installing the PostgreSQL Dependency, Hibernate with Panache and Hibernate Validator

This microservice:

  • interacts with a PostGreSQL database - so it needs a driver

  • uses Hibernate with Panache - so need the dependency on it

  • validates payloads and entities - so need a validator

  • consumes and produces JSON - so we need a mapper

Hibernate ORM is the de-facto JPA implementation and offers you the full breadth of an Object Relational Mapper. It makes complex mappings possible, but it does not make simple and common mappings trivial. Hibernate ORM with Panache focuses on making your entities trivial and fun to write in Quarkus.[3]

Because JPA and Bean Validation work well together, we will use Bean Validation to constrain our business model.

To add the required dependencies, just run the following command:

Call to action

$ ./mvnw quarkus:add-extension -Dextensions="jdbc-postgresql,hibernate-orm-panache,hibernate-validator,resteasy-jsonb"

This will add the following dependencies in the pom.xml file:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyPostgresql]

From now on, you can choose to either edit your pom directly or use the quarkus:add-extension command.

Running the Infrastructure

Before going further, be sure to run the infrastructure. To execute this service, you need a database (and later on we will need Prometheus and Kafka). Let’s use Docker and docker compose to ease the installation of such infrastructure.

You should already have installed the infrastructure into the infrastructure directory. Now, just execute docker-compose -f docker-compose.yaml up -d. You should see a few logs going on and then all the containers get started.

Call to action

On Linux, use the docker-compose-linux.yaml:

Listing 1. On Linux
docker-compose -f docker-compose-linux.yaml up -d

During the workshop, just leave all the containers up and running. Then, after the workshop, remember to shut them down using: docker-compose -f docker-compose.yaml down or docker-compose -f docker-compose-linux.yaml down on Linux.

Hero Entity

Call to action

To define a Panache entity, simply extend PanacheEntity, annotate it with @Entity and add your columns as public fields (no need to have getters and setters). The Hero entity should look like this:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/Hero.java[tags=adocEntity;!adocFindRandom]

Notice that you can put all your JPA column annotations and Bean Validation constraint annotations on the public fields.

Adding Operations

Thanks to Panache, once you have written the Hero entity, here are the most common operations you will be able to do:

// creating a hero
Hero hero = new Hero();
hero.name = "Superman";
hero.level = 9;

// persist it
hero.persist();

// getting a list of all Hero entities
List<Hero> heroes = Hero.listAll();

// finding a specific hero by ID
hero = Hero.findById(id);

// counting all heroes
long countAll = Hero.count();

But we are missing a business method: we need to return a random hero. For that it’s just a matter to add the following method to our Hero.java entity:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/Hero.java[tags=adocFindRandom]

You would need to add the following import statement if not done automatically by your IDE import java.util.Random;

Configuring Hibernate

Quarkus development mode is really useful for applications that mix front end or services and database access. We use quarkus.hibernate-orm.database.generation=drop-and-create in conjunction with import.sql so every change to your app and in particular to your entities, the database schema will be properly recreated and your data (stored in import.sql) will be used to repopulate it from scratch. This is best to perfectly control your environment and works magic with Quarkus live reload mode: your entity changes or any change to your import.sql is immediately picked up and the schema updated without restarting the application!

Call to action

For that, make sure to have the following configuration in your application.properties (located in src/main/resources):

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/resources/application.properties[tags=adocHibernate]

HeroService Transactional Service

To manipulate the Hero entity we will develop a transactional HeroService class. The idea is to wrap methods modifying the database (e.g. entity.persist()) within a transaction. Marking a CDI bean method @Transactional will do that for you and make that method a transaction boundary.

@Transactional can be used to control transaction boundaries on any CDI bean at the method level or at the class level to ensure every method is transactional. You can control whether and how the transaction is started with parameters on @Transactional:

  • @Transactional(REQUIRED) (default): starts a transaction if none was started, stays with the existing one otherwise.

  • @Transactional(REQUIRES_NEW): starts a transaction if none was started ; if an existing one was started, suspends it and starts a new one for the boundary of that method.

  • @Transactional(MANDATORY): fails if no transaction was started ; works within the existing transaction otherwise.

  • @Transactional(SUPPORTS): if a transaction was started, joins it ; otherwise works with no transaction.

  • @Transactional(NOT_SUPPORTED): if a transaction was started, suspends it and works with no transaction for the boundary of the method ; otherwise works with no transaction.

  • @Transactional(NEVER): if a transaction was started, raises an exception ; otherwise works with no transaction.

Call to action

Creates a new HeroService.java file in the same package with the following content:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroService.java[tags=adocTransactional;!adocConfigProperty;!adocPersistHeroLevel]

Notice that both methods that persist and update a hero, pass a Hero object as a parameter. Thanks to the Bean Validation’s @Valid annotation, the Hero object will be checked to see if it’s valid or not. It it’s not, the transaction will be rollback-ed.

Configuring the Datasource

Our project now requires a connection to a PostgreSQL database. The main way of obtaining connections to a database is to use a datasource. In Quarkus, the out of the box datasource and connection pooling implementation is Agroal.[4]

This is done in the src/main/resources/application.properties file.

Call to action

Just add the following datasource configuration:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/resources/application.properties[tags=adocDatasource]

HeroResource Endpoint

The HeroResource Endpoint was bootstrapped with only one method hello(). We need to add extra methods that will allow CRUD operations on heroes.

Call to action

Here are the new methods to add to the HeroResource class:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroResource.java[tags=adocResource;!adocOpenAPI;!adocMetrics,!adocMetricsImports,!adocOpenAPIImports]

Dependency Injection

Dependency injection in Quarkus is based on ArC which is a CDI-based dependency injection solution tailored for Quarkus' architecture.[5] You can learn more about it in the Contexts and Dependency Injection guide.[6]

ArC comes as a dependency of quarkus-resteasy so you already have it handy. That’s why you were able to use @Inject in the HeroResource to inject a reference to HeroService.

Adding Data

Call to action

To load some SQL statements when Hibernate ORM starts, add the following import.sql in the root of the resources directory. It contains SQL statements terminated by a semicolon. This is useful to have a data set ready for the tests or demos.

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/resources/import.sql[tag=adocSQL]

Ok, but that’s just a few entries. Download the SQL file import.sql and copy it under src/main/resources. Now, you have around 500 heroes that will be loaded in the database.

If you didn’t yet, start the application in dev mode:

$./mvnw quarkus:dev

Then, open your browser to http://localhost:8080/api/heroes. You should see lots of heroes…​

CRUD Tests in HeroResourceTest

To test the HeroResource endpoint, we will be using a QuarkusTestResource that will fire a Postgres database and then test CRUD operations. The QuarkusTestResource is a test extension that can configure the environment before running the application. In our context, we will be using TestContainers to start our database.[7]

Call to action

For that we will install the TestContainers dependency in our pom.xml as well as some extra test dependencies:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyTestContainers]
Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyTestREST]

Call to action

Then, you need to create the QuarkusTestResource. Create the io.quarkus.workshop.superheroes.hero.DatabaseResource classes with the following content:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/test/java/io/quarkus/workshop/superheroes/hero/DatabaseResource.java[]

This code just starts the database before the application, and set the quarkus.datasource.url system property to indicate to the application where is the test database. Note that the database would use a random port.

Call to action

Then, in io.quarkus.workshop.superheroes.hero.HeroResourceTest, you will add the following test methods to the HeroResourceTest class:

  • shouldNotGetUnknownHero: giving a random Hero identifier, the HeroResource endpoint should return a 204 (No content)

  • shouldGetRandomHero: checks that the HeroResource endpoint returns a random hero

  • shouldNotAddInvalidItem: passing an invalid Hero should fail when creating it (thanks to the @Valid annotation)

  • shouldGetInitialItems: checks that the HeroResource endpoint returns the list of heroes

  • shouldAddAnItem: checks that the HeroResource endpoint creates a valid Hero

  • shouldUpdateAnItem: checks that the HeroResource endpoint updates a newly created Hero

  • shouldRemoveAnItem: checks that the HeroResource endpoint deletes a hero from the database

The code is as follow:

Unresolved directive in rest-transaction-orm.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/test/java/io/quarkus/workshop/superheroes/hero/HeroResourceTest.java[tags=adocResourceTest;!adocOpenAPI;!adocHealth;!adocMetrics]

Let’s have a look to the 2 annotations used on the HeroResourceTest class. @QuarkusTest indicates that this test class is checking the behavior of a Quarkus application. The test framework starts the application before the test class and stops it once all the tests have been executed. The tests and the application runs in the same JVM, meaning that the test can be injected with application beans. This feature is very useful to test specific parts of the application. However in our case, we just execute HTTP requests to check the result.

Notice also the @QuarkusTestResource(DatabaseResource.class). It is how the QuarkusTestResource are attached to a test class.

Call to action

With this code written, execute the test using ./mvnw test. The test should pass.

Configuring the Hero Microservice

Hardcoded values in our code are a no go (even if we all did it at some point ;-)). In this guide, we learn how to configure our Hero API as well as some parts of Quarkus.

Configuring Logging

Run time configuration of logging is done through the normal application.properties file.

Unresolved directive in rest-configuration.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/resources/application.properties[tags=adocLogging]

Configuring Quarkus Listening Port

Because we will end-up running several microservices, let’s configure Quarkus so it listens to a different port than 8080: This is quite easy as we just need to add one property in the application.properties file:

Call to action

Unresolved directive in rest-configuration.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/resources/application.properties[tags=adocPort]

Changing the port is one of the rare configuration that cannot be done while the application is running.

Call to action

You would need to restart the application to change the port.

Hit CTRL+C to stop the application and restart it with: ./mvnw quarkus:dev

Injecting Configuration Value

When we persist a new hero we want to multiply its level by a value that can be configured. For this, Quarkus uses MicroProfile Config to inject the configuration in the application.[8] The injection uses the @ConfigProperty annotation.

When injecting a configured value, you can use @Inject @ConfigProperty or just @ConfigProperty. The @Inject annotation is not necessary for members annotated with @ConfigProperty, a behavior which differs from MicroProfile Config.

Call to action

Edit the HeroService, and introduce the following configuration properties:

Unresolved directive in rest-configuration.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroService.java[tag=adocConfigProperty]

You may need to add the following import statement if your IDE does not do it automatically: import org.eclipse.microprofile.config.inject.ConfigProperty;

  • If you do not provide a value for this property, the application startup fails with javax.enterprise.inject.spi.DeploymentException: No config value of type [int] exists for: level.multiplier

  • A default value (property defaultValue) is injected if the configuration does not provide a value for level.multiplier

Call to action

Now, modify the HeroService.persistHero() method to use the injected properties:

Unresolved directive in rest-configuration.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroService.java[tag=adocPersistHero]

Create the Configuration

By default, Quarkus reads application.properties.

Call to action

Edit the src/main/resources/application.properties with the following content:

Unresolved directive in rest-configuration.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/resources/application.properties[tag=adocConfigProperty]

Running and Testing the Application

Call to action

If you didn’t already, start the application with ./mvnw quarkus:dev. Once started, create a new hero with the following cUrl command:

$ curl -X POST -d  '{"level":5, "name":"Chewbacca", "powers":"Agility, Longevity"}'  -H "Content-Type: application/json" http://localhost:8083/api/heroes -v

< HTTP/1.1 201 Created
< Location: http://localhost:8083/api/heroes/952

As you can see, we’ve passed a level of 5 to create this new hero. The cUrl command returns the location of the newly created hero. Take this URL and do an HTTP GET on it. You will see that the level has been increased.

$ curl http://localhost:8083/api/heroes/952 | jq

{
  "id": 957,
  "level": 15,
  "name": "Chewbacca",
  "powers": "Agility, Longevity"
}

You may not know jq. It’s an amazing tool to manipulate JSON in the shell. More info on: https://stedolan.github.io/jq/

Hey! Wait a minute! Tests are failing now! Indeed, they don’t know the multiplier.

Call to action

In the application.properties file, add: %test.level.multiplier=1 which set the multiplier to 1 when running the tests. We will cover the %test syntax soon.

Open API

By default, a Quarkus application exposes its API description through an OpenAPI specification. Quarkus also lets you test it via a user-friendly UI named Swagger UI.

Directory Structure

In this module we will add extra class (HeroApplication) to the Hero API project. You will end-up with the following directory structure:

diag 1e2a229d689b3179ba4742bfd37b6b62

Installing the OpenAPI Dependency

Quarkus proposes a smallrye-openapi extension compliant with the Eclipse MicroProfile OpenAPI specification in order to generate your API OpenAPI v3 specification.[9]

Call to action

To install the OpenAPI dependency, just run the following command:

$ ./mvnw quarkus:add-extension -Dextensions="smallrye-openapi"

This will add the following dependency in the pom.xml file:

Unresolved directive in rest-openapi.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyOpenAPI]

Open API

Now, you curl http://localhost:8083/openapi endpoint:

$ curl http://localhost:8083/openapi
Unresolved directive in rest-openapi.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/openapi-origin.yaml[]

This contract lacks of documentation. The Eclipse MicroProfile OpenAPI allows you to customize the methods of your REST endpoint as well as the application.

Customizing Methods

The MicroProfile OpenAPI has a set of annotations to customize each REST endpoint method so the OpenAPI contract is richer and clearer for consumers:

  • @Operation: Describes a single API operation on a path.

  • @APIResponse: Corresponds to the OpenAPI Response model object which describes a single response from an API Operation

  • @Parameter: The name of the parameter.

  • @RequestBody: A brief description of the request body.

This is what the HeroResource endpoint looks like once annotated

Unresolved directive in rest-openapi.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroResource.java[tags=adocResource;adocOpenAPIImports;!adocMetrics;!adocMetricsImports]
Customizing the Application

The previous annotations allow you to customize the contract for a given REST Endpoint. But it’s also important to customize the entire application. The Microprofile OpenAPI also has a set of annotation to do so. The difference is that these annotations cannot be used on the Endpoint itself, but instead on another Java class configuring the entire application.

Call to action

For this, you need to create the src/main/java/io/quarkus/workshop/superheroes/hero/HeroApplication class with the following content:

Unresolved directive in rest-openapi.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroApplication.java[tags=adocApplication]
Customized Contract

If you go back to the http://localhost:8083/openapi endpoint you will see the following OpenAPI contract:

Unresolved directive in rest-openapi.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/openapi.yaml[]

Swagger UI

When building APIs, developers want to test them quickly. Swagger UI is a great tool permitting to visualize and interact with your APIs.[10] The UI is automatically generated from your OpenAPI specification. The Quarkus smallrye-openapi extension comes with a swagger-ui extension embedding a properly configured Swagger UI page. By default, Swagger UI is accessible at /swagger-ui. So, once your application is started, you can go to http://localhost:8083/swagger-ui and play with your API.

Call to action

You can visualize your API’s operations and schemas.

rest openapi swaggerui

For example, you can try the /api/heroes/random endpoint to retrieve a random hero.

OpenAPI Tests in HeroResourceTest

Call to action

Let’s add a few extra test methods in HeroResourceTest that would make sure OpenAPI and Swagger UI are packaged in the application:

Unresolved directive in rest-openapi.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/test/java/io/quarkus/workshop/superheroes/hero/HeroResourceTest.java[tags=adocOpenAPI]

Call to action

Execute the test using ./mvnw test.

If you have any problem with the code, don’t understand or feel you are running, remember to ask for some help. Also, you can get the code of this entire workshop from https://github.com/quarkusio/quarkus-workshops/tree/master/quarkus-super-heroes-workshop.

Quarkus


In the previous chapter, you had a quick peek to Quarkus and how you can build HTTP / REST-based applications with it. But that was just the beginning, Quarkus can do a lot more, and this is the purpose of this chapter. In this chapter, you are going to see:

  • What’s Quarkus? and how does it change the Java landscape

  • What are the main Quarkus idea and how it helps in the cloud native world

  • The Quarkus build process, in other words, the secret sauce

  • Some Quarkus features such as the application lifecycle support

  • How you can use Quarkus to generate native executable

What’s Quarkus?

Java was born more than 20 years ago. The world 20 years ago was quite different. The software industry has gone through several revolutions over these two decades. Java has always been able to reinvent itself to stay relevant.

But a new revolution is happening right now. While for years, most applications were running on huge machines, with lots of CPU and memory, they are now running on the Cloud, in constrained environments, in containers, where the resources are shared. Density is the new optimization: crank as many mini-apps (or microservices) as possible per node. And scale by adding more instances of an app instead of a more powerful single instance.

The Java ergonomics, designed 20 years ago, do not fit well in this new environment. Java applications were designed to run 24/7 for months, even years. The JIT is optimizing the execution over time; the GC manages the memory efficiently…​ But all these features have a cost, and the memory required to run Java applications and startup times are showstoppers when instead of one application, you deploy 20 or 50 microservices. The issue is not the JVM itself; it’s also the Java ecosystem that needs to be reinvented.

That’s where Quarkus, and other projects, enter the game. Quarkus proposes to generalize "Ahead of Time" techniques.[11] When a Quarkus application is built, some work that usually happens at runtime is moved to the build time. Thus, when the application runs, everything has been pre-computed, and all the annotation scanning, XML parsing, and so on won’t be executed anymore. It has two direct benefits: on the startup time (a lot faster) and on memory consumption (a lot lower).

quarkus augmentation

So, as depicted on the figure above, Quarkus does bring an infrastructure for frameworks to embrace build time metadata discovery (like annotations), declare which classes need reflection at runtime, boot at build time, and generally offer a lot GraalVM optimization for free (or cheap at least). Indeed, thanks to all these metadata, Quarkus can configure native compilers such as the SubstrateVM compiler to generate a native executable for your Java application. Thanks to an aggressive dead-code elimination, the final executable is smaller, faster to start and use a ridiculously small amount of memory.

quarkus native compilation

Quarkus does not stop there. As you have seen in the previous chapter, it proposes a stellar developer experience. It also unifies reactive and imperative so that you can mix regular JAX-RS and event-oriented code in the same application. Finally, Quarkus is based on many popular framework out there such as Eclipse Vert.x, Apache Camel, Undertow…​ You can already state that you have 5 years of experience with Quarkus.

Ok, but enough talking, time to see this in action.

Quarkus Augmentation

Let’s demystify all this.

So far, you have developed the superheroes microservice. This microservice is relatively simple, but it still has database access, ORM support, transaction, JSON serialization, and deserialization.

Call to action

Let’s now package this application using:

$ mvn package

In the log, you can see actions happening at build time during what Quarkus call the augmentation phase.

[INFO] --- quarkus-maven-plugin:1.2.0.Final:build (default) @ rest-hero ---
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Beginning quarkus augmentation
[INFO] [org.jboss.threads] JBoss Threads version 3.0.0.Final
[INFO] [org.hibernate.jpa.boot.internal.PersistenceXmlParser] HHH000318: Could not find any META-INF/persistence.xml file in the classpath
[INFO] [org.hibernate.Version] HHH000412: Hibernate Core {5.4.5.Final}
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 2653ms

In this log, you can see that the Hibernate XML parser has been executed at build time. This saves from having to:

  1. embed an XML parser at runtime,

  2. Do the actual parsing,

  3. Configure Hibernate based on the content of the file.

With Quarkus, at runtime, almost everything is already configured. Only runtime configuration properties are applied at startup (such as database URLs).

Also, during this augmentation, Java classes are generated or extended. Remember the Hero Panache entity. The class is extended during the augmentation. If you run javap target/transformed-classes/io/quarkus/workshop/superheroes/hero/Hero.class, you can see methods prefixed with $$, which have been added to the class.

If now you look at the target/wiring-classes/io/quarkus/workshop/superheroes/hero, you can see many generated classes.

All these metadata are computed and managed by extensions. The next figure present some of the extension you already used, but there are a lot more. We are going to learn more about extensions later in this workshop, and even build one. What’s important to understand for now is that the magic is packaged into extension and every time you add a quarkus- dependency to your pom.xml file, you enable an extension.

quarkus extensions

Application Lifecycle

Now that you know how is structured Quarkus, let’s continue using various extensions. You often need to execute custom actions when the application starts and clean up everything when the application stops. In this module we will display a banner in the logs once the Hero API has started.

Directory Structure

In this module we will add an extra class (HeroApplicationLifeCycle) to handle the Hero API lifecycle. You will end-up with the following directory structure:

diag e17b7589509787554c6f1510afde58c5

Displaying a Banner

When our application starts, the logs are pretty boring…​ and lack of a banner (any decent application must have a banner nowadays). So the first thing that you need to do is to go to the following website and pick up your favourite "Hero API" text banner.

Call to action

Create a new class named HeroApplicationLifeCycle (or pick another name, the name does not matter) in the io.quarkus.workshop.superheroes.hero package, and copy your banner so you end up with a similar content:

Unresolved directive in quarkus-lifecycle.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroApplicationLifeCycle.java[tags=adocApplicationLifeCycle;!adocProfileManager]

Thanks to the CDI @Observes, the HeroApplicationLifeCycle is invoked:

  • on startup with the StartupEvent so it can execute code (here, displaying the banner) when the application is starting

  • on shutdown with the ShutdownEvent when the application is terminating

Call to action

Run the application with: ./mvnw quarkus:dev, the banner is printed to the console. When the application is stopped, the second log message is printed.

If your application was still running, just send an HTTP request, like go to http://localhost:8083. As the application code changed, the application is restarted.

Configuration Profiles

Quarkus supports the notion of configuration profiles. These allow you to have multiple configuration in the same file and select between them via a profile name.

By default Quarkus has three profiles, although it is possible to use as many as you like. The default profiles are:

  • dev - Activated when in development mode (i.e. quarkus:dev)

  • test - Activated when running tests

  • prod - The default profile when not running in development or test mode

Let’s change the HeroApplicationLifeCycle so it displays the current profile.

Call to action

For that, just add a log invoking the ProfileManager.getActiveProfile() method:

Unresolved directive in quarkus-profile.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroApplicationLifeCycle.java[tags=adocStartupEvent]

If not already done, you need to add the following import statement: import io.quarkus.runtime.configuration.ProfileManager;

In the application.properties file, you can prefix a property to be defined in the running profile. For example, we did add the %test.level.multiplier=1 property in the previous chapter. This indicates that the property level.multiplier is set to 1 in the test profile.

Now, if you start your application in dev mode with mvn compile quarkus:dev, you will get the dev profile enabled. If you start the tests, the test profile is enabled (and so the multiplier is set to 1).

Call to action

Package your application with mvn package, and start it with java -Dquarkus.profile=foo -jar target/rest-hero-1.0-SNAPSHOT-runner.jar. You will see that the foo profile is enabled. As not overridden, the level.multiplier property has the value 3.

Profiles are very useful to customize the configuration per environment. We are going to see an example of such customization in the next section.

From Java to Native

Building a Native Executable

Let’s now produce a native executable for our application. As explained in the introduction of this chapter, Quarkus is able to generate native executables. Just like Go, native executable don’t need a VM to run, they contain the whole application, like an .exe file on Windows.

It improves the startup time of the application, and produces a minimal disk footprint. The executable would have everything to run the application including the "JVM" (shrunk to be just enough to run the application), and the application.

Choosing JVM execution vs native executable execution depends on your application needs and environment. Discuss with the lab organizers for some insights

To do so, you will find in the pom.xml the following profile:

<profile>
  <id>native</id>
  <activation>
    <property>
      <name>native</name>
    </property>
  </activation>
  <build>
    <plugins>
      <plugin>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-maven-plugin</artifactId>
        <version>${quarkus.version}</version>
        <executions>
          <execution>
            <goals>
              <goal>native-image</goal>
            </goals>
            <configuration>
              <enableHttpUrlHandler>true</enableHttpUrlHandler>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>${surefire-plugin.version}</version>
        <executions>
          <execution>
            <goals>
              <goal>integration-test</goal>
              <goal>verify</goal>
            </goals>
            <configuration>
              <systemProperties>
                <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
              </systemProperties>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  <properties>
    <quarkus.package.type>native</quarkus.package.type>
  </properties>
</profile>

Make sure you have the GRAALVM_HOME environment variable defined and pointing to where you installed GraalVM.

Call to action

Then create a native executable using: ./mvnw package -Pnative. In addition to the regular files (rest-hero-1.0-SNAPSHOT.jar and rest-hero-1.0-SNAPSHOT-runner.jar), the build also produces target/rest-hero-1.0-SNAPSHOT-runner (notice that there is no .jar file extension). You can run it using: ./target/rest-hero-1.0-SNAPSHOT-runner.

Creating a native executable requires a lot of memory and CPU. It also takes a few minutes, even for simple application like the Hero microservice. Most of the time is spent during the dead code elimination, as it traverse the whole (closed) world.

Testing the Native Executable

Producing a native executable can lead to a few issues, and so it’s also a good idea to run some tests against the application running in the native file. In the pom.xml file, the native profile contains:

<plugin>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>${surefire-plugin.version}</version>
  <executions>
    <execution>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <configuration>
        <systemProperties>
          <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
        </systemProperties>
      </configuration>
    </execution>
  </executions>
</plugin>

This instructs the failsafe-maven-plugin to run integration-test and indicates the location of the produced native executable.

Call to action

Then, open the src/test/java/io/quarkus/workshop/superheroes/hero/NativeHeroResourceIT.java and update it with the following:

Unresolved directive in quarkus-native.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/test/java/io/quarkus/workshop/superheroes/hero/NativeHeroResourceIT.java[tags=adocResourceIT;!adocHealth;!adocMetrics]

Instead of using @QuarkusTest, it uses the @NativeImageTest test runner that starts the application from the native file before the tests. The executable is retrieved using the native.image.path system property configured in the Failsafe Maven Plugin.

Notice that NativeHeroResourceIT does not extend HeroResourceTest. It is a good practice to share the same tests for native and JVM mode that way but in our case the behavior is a bit different. For example, we do not want Swagger UI exposed in our native image.

Also notice that this class also use the @QuarkusTestResource to start and stop the database.

Call to action

To see the NativeHeroResourceIT run against the native executable, use ./mvnw verify -Pnative:

[INFO] --- maven-failsafe-plugin:2.22.0:integration-test (default) @ rest-hero ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running io.quarkus.workshop.superheroes.hero.NativeHeroResourceIT
...
20:42:59 INFO  [io.qu.wo.su.he.HeroApplicationLifeCycle] (main)   _   _                      _    ____ ___
20:42:59 INFO  [io.qu.wo.su.he.HeroApplicationLifeCycle] (main)  | | | | ___ _ __ ___       / \  |  _ \_ _|
20:42:59 INFO  [io.qu.wo.su.he.HeroApplicationLifeCycle] (main)  | |_| |/ _ \ '__/ _ \     / _ \ | |_) | |
20:42:59 INFO  [io.qu.wo.su.he.HeroApplicationLifeCycle] (main)  |  _  |  __/ | | (_) |   / ___ \|  __/| |
20:42:59 INFO  [io.qu.wo.su.he.HeroApplicationLifeCycle] (main)  |_| |_|\___|_|  \___/   /_/   \_\_|  |___|
20:42:59 INFO  [io.qu.wo.su.he.HeroApplicationLifeCycle] (main)                          Powered by Quarkus
...
[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.068 s - in io.quarkus.workshop.superheroes.hero.NativeHeroResourceIT
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-failsafe-plugin:2.22.0:verify (default) @ rest-hero ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  05:29 min
[INFO] Finished at: 2019-10-18T20:43:03+02:00
[INFO] ------------------------------------------------------------------------

One Microservice is no Microservices


So far we’ve built one microservice. In the following sections you will develop two extra microservices: a villain microservice, a mad copycat of the hero microservice, and a fight microservice where heroes and villains fight. We will also add an Angular front-end so we can fight graphically.

diag c9b2025ae67f56dbff18f005e46bacb9

Each microservice is developed in it’s own directory.

diag 63bbf18740e15a748c98f1d1976a4ecd

Villain Microservice

New microservice, new project! In this section we will see the counterpart of the Hero microservice: the Villain microservice! The Villain REST Endpoint is really similar to the Hero Endpoint.

The code has already been provided in the /super-heroes/rest-villain/ directory. There is almost no differences with the hero microservice, just that it provides super villains instead and uses the port 8084.

Directory Structure

As for the hero microservice, you have the following directory structure:

diag d9addfd28ea95157d04ff7b962f91fe4

If you look at the code, it’s very similar to the hero microservice.

Running, Testing and Packaging the Application

Call to action

First, make sure the tests pass by executing the command ./mvnw test (or from your IDE).

Now that the tests are green, we are ready to run our application.

Call to action

Use ./mvnw quarkus:dev to start it (notice the nice banner). Once the application is started, create a new villain with the following cUrl command:

$ curl -X POST -d  '{"level":2, "name":"Darth Vader", "powers":"Darkness, Longevity"}'  -H "Content-Type: application/json" http://localhost:8084/api/villains -v

< HTTP/1.1 201 Created
< Location: http://localhost:8084/api/villains/582

The cUrl command returns the location of the newly created villain. Take this URL and do an HTTP GET on it.

$ curl http://localhost:8084/api/villains/582 | jq

{
  "id": 582,
  "level": 4,
  "name": "Darth Vader",
  "powers": "Darkness, Longevity"
}

Remember that you can also check Swagger UI by going to http://localhost:8084/swagger-ui.

Fight Microservice

Ok, let’s develop another microservice. We have a REST API that returns a random Hero. Another REST API that returns a random Villain…​ we need a new REST API that invokes those two, gets one random hero and one random villain and makes them fight. Let’s call it the Fight API.

Bootstrapping the Fight REST Endpoint

Like for the Hero and Villain API, the easiest way to create this new Quarkus project is to use a Maven archetype. Under the quarkus-super-heroes-workshop/super-heroes root directory where you have all your code.

Call to action

Open a terminal and run the following command:

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/bootstrap.sh[tag=adocSnippet]

Call to action

Also add Testcontainers and other test-related dependencies to your pom.xml.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.12.2</version>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.12.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>kafka</artifactId>
    <version>1.12.2</version>
</dependency>
<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-library</artifactId>
    <scope>test</scope>
</dependency>
Prefering Web UI

Instead of the Maven command, you can use https://code.quarkus.io.

You can see that beyond the extensions we have used so far, we added the Kafka support which uses Eclipse MicroProfile Reactive Messaging. Stay tuned.

Directory Structure

At the end you should have the following directory structure:

diag 16daea77c7c8cca06c03a0a0a417f6e7

Fight Entity

A fight is between a hero and a villain. Each time there is a fight, there is a winner and a loser. So the Fight entity is there to store all these fights.

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/Fight.java[tags=adocEntity;!adocSkip]

Fighters Bean

Now comes a trick. The Fight REST API will ultimatelly invoke the Hero and Villain APIs (next sections) to get two random fighters. The Fighters class has one Hero and one Villain. Notice that Fighters is not an entity, it is not persisted in the database, just marshalled and unmarshalled to JSon.

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/Fighters.java[tags=adocBean;!adocSkip]

The Fight REST API is just interested in the hero’s name, level, picture and powers (not the other name as described in the Hero API). So the Hero bean looks like this (notice the client subpackage):

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/client/Hero.java[tags=adocBean;!adocSkip]

Villain is pretty similar (also in the client subpackage):

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/client/Villain.java[tags=adocBean;!adocSkip]

So, these classes are just used to map the results from the Hero and Villain microservices.

FightService Transactional Service

To transactionnally manipulate the Fight entity we need a FightService. Notice the persistFight method. This method is the one creating a fight between a hero and a villain. As you can see the algorithm to determine the winner is a bit random (even though it uses the levels). If you are not happy about the way the fight operates, choose your own winning algorithm ;o)

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/FightService.java[tags=adocTransactional;!adocRestClient;!adocKafka;!adocKafkaEmitter;!adocFallback]

Call to action

For now, just implement an empty Fighters findRandomFighters() method which returns null. Later, this method will invoke the Hello and Villain API to get a random Hello and random Villain. So for now something like the following is enough:

public Fighters findRandomFighters() {
    // Will be implemented later
    return null;
}

FightResource Endpoint

To expose a REST API we also need a FightResource (with OpenAPI annotations of course).

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/FightResource.java[tags=adocResource;!adocMetrics;!adocTimeout]

FightApplication for OpenAPI

The FightApplication class is just there to customize the OpenAPI contract.

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/FightApplication.java[tag=adocApplication]

Notice that there is no FightApplicationLifeCycle class. We will use a Quarkus extension later on to display a banner for Fight.

Adding Data

Call to action

To load some SQL statements when Hibernate ORM starts, download the SQL file import.sql and copy it under src/main/resources.

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/resources/import.sql[tag=adocSQL]
...

Configuration

As usual, we need to configure the application.

Call to action

In the application.properties file add:

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/resources/application.properties[tags=!adocCORS;!adocRestClient;!adocKafka;!adocBanner]

Note that the fight service uses the port 8082.

FightResourceTest Test Class

We need to test our REST API.

Call to action

For that, copy the following FightResourceTest class under the src/test/java/io/quarkus/workshop/superheroes/fight directory.

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/test/java/io/quarkus/workshop/superheroes/fight/FightResourceTest.java[tags=adocResourceTest;!adocHealth;!adocMetrics;!adocRandom]

You would also need, not only one but 2 QuarkusTestResources, one for the database and one for Kafka. We could have merged both in a single class, but we wanted to illustrate the possibility to have more than one resources.

Call to action

So, create the DatabaseResource class in the same package as the FightResourceTest class, and add the following content:

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/test/java/io/quarkus/workshop/superheroes/fight/DatabaseResource.java[]

Call to action

Then, create the KafkaResource class with the following content:

Unresolved directive in microservices-fight.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/test/java/io/quarkus/workshop/superheroes/fight/KafkaResource.java[]

Call to action

Also, delete the generated NativeFightResourceIT class, as we won’t run native test for this microservice.

Running, Testing and Packaging the Application

Call to action

First, make sure the tests pass by executing the command ./mvnw test (or from your IDE).

Now that the tests are green, we are ready to run our application. Use ./mvnw quarkus:dev to start it (notice that there is no banner yet, it will come later). Once the application is started, just check that it returns the fights from the database with the following cUrl command:

$ curl http://localhost:8082/api/fights

Remember that you can also check Swagger UI by going to http://localhost:8082/swagger-ui.

User Interface

Now that we have the three main microservices, time to have a decent user interface to start fighting. The purpose of this workshop is not to develop a web interface and learn yet another web framework. This time you will just download an Angular application, install it, and run it on another Quarkus instance.

The Web Application

Navigate to the super-heroes/ui-super-heroes/ui-super-heroes directory. It contains the code of the microservice. Being an Angular application, you will find a package.json file which defines all the needed dependencies. Notice that there is a pom.xml file. This is just a convenient way to install NodeJS and NPM so we can build the Angular application with Maven. The pom.xml also allows us to package the Angular application into Quarkus.

If you are not in a frontend mood, just scroll to Installing the Web Application on Quarkus

Looking at Some Code (optional)

You don’t need to be an Angular expert, but there are some pieces of code that are worth looking at. If you look under the src/app/shared directory, you will find an api and a model sub-directory. Let’s look at fight.ts.

Unresolved directive in microservices-ui.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/ui-super-heroes/src/app/shared/model/fight.ts[tag=adocModel]

As you can see, it matches our Fight Java class. Same for fighters.ts, hero.ts or villain.ts. Under api there is the fight.service.ts that defines all the methods to access to our Fight REST API through HTTP.

Unresolved directive in microservices-ui.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/ui-super-heroes/src/app/shared/api/fight.service.ts[tag=adocService]

Well, guess what? We didn’t have to type this code either. It was generated thanks to a tool called swagger-codegen.[12] Because our Fight REST API exposes an OpenAPI contract, swagger-codegen just swallows it, and generates the TypeScript code to access it. It’s just a matter of running:

$ swagger-codegen generate -i http://localhost:8082/openapi -l typescript-angular -o src/app/shared

Here, you see another advantage of exposing an OpenAPI contract: it documents the API which can be read by a human, or processed by tools.

Installing the Web Application on Quarkus

Thanks to the frontend-maven-plugin plugin declared on the pom.xml, we can use a good old Maven command to install and build this Angular application.

Call to action

Execute mvn install and Maven will download and install Node JS and NPM and build the application. You should now have a node_modules directory with all the Angular dependencies. At this stage, make sure the following commands work:

ng version (or ./node_modules/.bin/ng version)
node -v    (or ./node/node -v)

To install the Angular application into a Quarkus instance, we just build the app and copy the bundles under the resources/META-INF/resources directory. Look at the package.sh, that’s exactly what it does.

Unresolved directive in microservices-ui.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/ui-super-heroes/package.sh[tag=adocShell]

Call to action

Execute the package.sh script. You will see all the Javascript files under resources/META-INF/resources directory. We are now ready to go.

If the ng command does not work because it can’t find node, there is a little hack to solve it. Open the file ui-super-heroes/node_modules/.bin/ng and change the shebang line from !/usr/bin/env node to !/usr/bin/env ./node/node. This way ng knows it has to use NodeJS installed under the ui-super-heroes/node directory

Running the Web Application

Call to action

As usual, use mvn quarkus:dev to start the web application.

Be sure you have the hero and villain microservices running (dev mode is enough).

Once the application is started, go to http://localhost:8080 (8080 is the default Quarkus port as we didn’t change it in the application.properties this time). It should display the main web page.

angular ui

Oups, not working yet! Not even the pictures, we must have been forgotten something! Let’s move on to the next section then and make the application work.

CORS

Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.[13] So when we want our heroes and villains to fight, we actually cross several origins: we go from localhost:8080 (the UI) to localhost:8082 (Fight API) which invokes localhost:8083 (Hero) and localhost:8084 (Villain). If you look at the console of your Browser you should see something similar to this:

cors

Quarkus comes with a CORS filter which intercepts all incoming HTTP requests. It can be enabled in the Quarkus configuration file:

Unresolved directive in microservices-cors.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/resources/application.properties[tags=adocCORS]

If the filter is enabled and an HTTP request is identified as cross-origin, the CORS policy and headers defined using the following properties will be applied before passing the request on to its actual target (servlet, JAX-RS resource, etc.):

Property Description

quarkus.http.cors.origins

The comma-separated list of origins allowed for CORS. The filter allows any origin if this is not set.

quarkus.http.cors.methods

The comma-separated list of HTTP methods allowed for CORS. The filter allows any method if this is not set.

quarkus.http.cors.headers

The comma-separated list of HTTP headers allowed for CORS. The filter allows any header if this is not set.

quarkus.http.cors.exposed-headers

The comma-separated list of HTTP headers exposed in CORS.

quarkus.http.cors.access-control-max-age

The duration indicating how long the results of a pre-flight request can be cached. This value will be returned in a Access-Control-Max-Age response header.

Call to action

So make sure you set the quarkus.http.cors property to true on the:

  1. Fight microservice,

  2. Hero microservice,

  3. Villain microservice

But, even with this, the UI is still not working. The explanation is simple, we forgot another thing:

diag f44d76d59eea57d7c7c2debf5e432cb3

Remember the function to retrieve random fighters. We are currently returning null. Let’s move to the next session to see how we can implement this method.

HTTP communication & Fault Tolerance


So far we’ve built one Fight microservice which need to invoke the Hero and Villain microservices. In the following sections you will develop this invocation thanks to the MicroProfile REST Client. We will also deal with fault tolerance thanks to timeouts and circuit breaker.

diag c9b2025ae67f56dbff18f005e46bacb9

REST Client

This chapter explains how to use the MicroProfile REST Client in order to interact with REST APIs with very little effort.[14]

Directory Structure

Remember the structure of the Fight microservice:

diag ec0d26f7d3ee93d01d772aa4a37e67b9

We are going to rework the:

  • FightService class

  • FightResourceTest class

  • application.properties

Installing the REST Client Dependency

Call to action

To install the MicroProfile REST Client dependency, just run the following command:

$ ./mvnw quarkus:add-extension -Dextensions="rest-client"

This will add the following dependency in the pom.xml file:

Unresolved directive in fault-tolerance-rest-client.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/pom.xml[tags=adocRestClientDependency]

FightService Invoking External Microservices

Remember that in the previous sections we left the FightService.findRandomFighters() method returns null. We have to fix this. What we actually want is to invoke both the Hero and Villain APIs, asking for a random hero and a random villain.

Call to action

For that, replace the findRandomFighters method with the following code to the FightService class:

Unresolved directive in fault-tolerance-rest-client.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/FightService.java[tags=adocRestClient;!adocFallback]

Note that in addition to the standard CDI @Inject annotation, we also need to use the MicroProfile @RestClient annotation to inject HeroService and VillainService.

If not done automatically by your IDE, add the following import statement: import org.eclipse.microprofile.rest.client.inject.RestClient;

Creating the Interfaces

Using the MicroProfile REST Client is as simple as creating an interface using the proper JAX-RS and MicroProfile annotations.

Call to action

In our case both interfaces should be created under the client subpackage and have the following content:

Unresolved directive in fault-tolerance-rest-client.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/client/HeroService.java[tag=adocRestClient]

The findRandomHero method gives our code the ability to query a random hero from the Hero REST API. The client will handle all the networking and marshalling leaving our code clean of such technical details.

The purpose of the annotations in the code above is the following:

  • @RegisterRestClient allows Quarkus to know that this interface is meant to be available for CDI injection as a REST Client

  • @Path and @GET are the standard JAX-RS annotations used to define how to access the service

  • @Produces defines the expected content-type

The VillainService is very similar and looks like this:

Unresolved directive in fault-tolerance-rest-client.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/client/VillainService.java[tag=adocRestClient]

Call to action

Once created, go back to the FightService class and add the following import statements:

import io.quarkus.workshop.superheroes.fight.client.HeroService;
import io.quarkus.workshop.superheroes.fight.client.VillainService;

Configuring REST Client Invocation

Call to action

In order to determine the base URL to which REST calls will be made, the REST Client uses configuration from application.properties. The name of the property needs to follow a certain convention which is best displayed in the following code:

Unresolved directive in fault-tolerance-rest-client.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/resources/application.properties[tag=adocRestClient]

Having this configuration means that all requests performed using HeroService will use http://localhost:8083 as the base URL. Using this configuration, calling the findRandomHero method of HeroService would result in an HTTP GET request being made to http://localhost:8083/api/heroes/random.

Having this configuration means that the default scope of HeroService will be @Singleton. Supported scope values are @Singleton, @Dependent, @ApplicationScoped and @RequestScoped. The default scope is @Dependent. The default scope can also be defined on the interface.

Now, go back in the UI and refresh, you should see some pictures!

Updating the Test with Mock Support

But, now we have another problem. To run the tests of the Fight API we need the Hero and Villain REST APIs to be up and running. To avoid this, we need to Mock the HeroService and VillainService interfaces.

Quarkus supports the use of mock objects using the CDI @Alternative mechanism.[15]

Call to action

To use this simply override the bean you wish to mock with a class in the src/test/java directory, and put the @Alternative and @Priority(1) annotations on the bean. Alternatively, a convenient io.quarkus.test.Mock stereotype annotation could be used. This built-in stereotype declares @Alternative, @Priority(1) and @Dependent. So, to mock the HeroService interface we just need to implement the following MockHeroService class:

Unresolved directive in fault-tolerance-rest-client.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/test/java/io/quarkus/workshop/superheroes/fight/client/MockHeroService.java[tag=adocTest]

Call to action

Do the same for the MockVillainService:

Unresolved directive in fault-tolerance-rest-client.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/test/java/io/quarkus/workshop/superheroes/fight/client/MockVillainService.java[tag=adocTest]

Call to action

Finally, edit the FightResourceTest and add the following method:

Unresolved directive in fault-tolerance-rest-client.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/test/java/io/quarkus/workshop/superheroes/fight/FightResourceTest.java[tag=adocRandom]

You would need the following import statements:

import io.quarkus.workshop.superheroes.fight.client.MockHeroService;
import io.quarkus.workshop.superheroes.fight.client.MockVillainService;

Running and Testing the Application

Call to action

First, make sure the tests pass by executing the command ./mvnw test (or from your IDE).

Now that the tests are green, we are ready to run our application. Use ./mvnw compile quarkus:dev to start it. Once the application is started, go to http://localhost:8080 and start fighting (finally !).

Fallbacks (optional)

This chapter is optional, you can jump to the Timeout (optional) chapter, Observability (optional) chapter or to the Event-driven and Reactive microservices chapter if you already know about fault-tolerance and fallback.

So now you’ve been playing this great Super Heroes Fight for a few hours…​ and you kill the Hero REST API. What happens? Well, the Fight REST API cannot invoke the Hero API anymore and breaks with the following exception:

ERROR [io.qu.ve.ht.ru.QuarkusErrorHandler] HTTP Request to /api/fights/randomfighters failed:
org.jboss.resteasy.spi.UnhandledException: javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: java.net.ConnectException: Connection refused (Connection refused)
at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)
at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:372)
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:209)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:496)

One of the challenges brought by the distributed nature of microservices is that communication with external systems is inherently unreliable. This increases demand on resiliency of applications. To simplify making more resilient applications, Quarkus contains an implementation of the MicroProfile Fault Tolerance specification.[16]

Installing the Fault Tolerance Dependency

To install the MicroProfile Fault Tolerance dependency, just run the following command:

Call to action

$ ./mvnw quarkus:add-extension -Dextensions="smallrye-fault-tolerance"

This will add the following dependency in the pom.xml file:

Unresolved directive in fault-tolerance-fallbacks.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/pom.xml[tags=adocFaultToleranceDependency]

Adding Fallbacks

Let’s make our find random fighters feature better by providing a fallback way of getting a dummy hero or villain in case of failure.

Call to action

For that, add two fallback methods to the FightService and a @Fallback annotation to both findRandomHero and findRandomVillain methods as follows:

Unresolved directive in fault-tolerance-fallbacks.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/FightService.java[tags=adocRestClient,adocFallback]

Call to action

Also add the import org.eclipse.microprofile.faulttolerance.Fallback; statement.

Running the Application

Now we are ready to run our application and test the fallbacks.

Call to action

For that, kill the Hero (and/or the Villain API) and start playing again. You should see the following:

fault tolerance fallback

Restart the Hero REST API…​ and keep on playing. Super heroes are back to the fight!

Timeout (optional)

This chapter is optional, you can jump to the Observability (optional) chapter or to the Event-driven and Reactive microservices chapter if you already know about fault-tolerance and timeout.

Sometimes invoking a REST API can take a long time. In fact, the more microservices invoke other microservices, the more network latency you can have. And what happens when a HTTP request takes long? Well, if hangs. On your browser you can see the request pending if you turn on the dev tools and look at what’s the network is doing.

fault tolerance pending

Adding Timeouts

Getting random fighters can take longer than expected.

Call to action

To simulate a long running process, update the FightResource with the following code:

Unresolved directive in fault-tolerance-timeout.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/FightResource.java[tags=adocFaultTolerance;!adocMetrics]

Don’t forget to add the following import: import org.eclipse.microprofile.config.inject.ConfigProperty;

Let’s say we’ve added some new functionality in the veryLongProcess method. When the process is really too long and the system is overloaded, we would rather time out.

Here we throw the InterruptedException in the method signature. But what you would have to do in real life is to catch it, and propose a fallback algorithm when the process times out.

Call to action

Note that the timeout was configured to 250 ms, and a Thread.sleep was introduced and can be configured in the application.properties. If we set the property to a higher value than the timeout, let’s say 10.000, then the request should be interrupted.

process.milliseconds=10000

Running the Application

Now that you have set the number of waiting milliseconds to 10.000, run the application and start fighting again. You should see the following:

ERROR [io.qu.ve.ht.ru.QuarkusErrorHandler] HTTP Request to /api/fights/randomfighters failed:
org.jboss.resteasy.spi.UnhandledException: org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException:
com.netflix.hystrix.exception.HystrixRuntimeException: io_quarkus_workshop_superheroes_fight_FightResource#getRandomFighters() timed-out and no fallback available.
at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)

Call to action

Before going further, set the process.milliseconds to 0.

If you have any problem with the code, don’t understand or feel you are running, remember to ask for some help. Also, you can get the code of this entire workshop from https://github.com/quarkusio/quarkus-workshops/tree/master/quarkus-super-heroes-workshop.

Observability (optional)

This chapter is optional. It covers health checks, metrics and prometheus. You can jump to the Event-driven and Reactive microservices chapter if you want.


Now that we have several microservices, observing them starts to be a bit tricky: we can’t just look at the logs of all the microservices to see if they are up and running or behaving correctly. In the following sections you will add health checks and several metrics to the Fight, Hero and Villain APIs and gather them within Promotheus.

diag bac3d4f08b371164a1043d83b980832d

Health Check

Quarkus applications can utilize the MicroProfile Health specification through the SmallRye Health extension. The MicroProfile Health allows applications to provide information about their state to external viewers which is typically useful in cloud environments where automated processes must be able to determine whether the application should be discarded or restarted.[17]

Directory Structure

In this module we will add an extra subdirectory with two classes to handle the Health Check. You will end-up with the following directory structure:

diag 498786c562fdd24a92a0460ff8c2454e

While you could add health checks to all our microservices, we are focusing on the Hero microservice. So don’t forget to switch to the Hero microservice code. You can apply the same to the other microservices.

Installing the Health Dependency

Call to action

To install the MicroProfile Health dependency, just run the following command:

$ ./mvnw quarkus:add-extension -Dextensions="health"

This will add the following dependency in the pom.xml file:

Unresolved directive in observability-healthcheck.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyHealth]

Running the Default Health Check

Importing the smallrye-health extension directly exposes three REST endpoints:

  • /health/live - The application is up and running.

  • /health/ready - The application is ready to serve requests.

  • /health - Accumulating all health check procedures in the application.

To check that the smallrye-health extension is working as expected, access using your browser or cURL:

All of the health REST endpoints return a simple JSON object with two fields:

  • status — the overall result of all the health check procedures

  • checks — an array of individual checks

The general status of the health check is computed as a logical AND of all the declared health check procedures. The checks array is empty as we have not specified any health check procedure yet so let’s define some.

Adding Liveness

To check that our Hero API application is live, we can check that the HeroResource.hello() method works.

Call to action

For that, this is the PingHeroResourceHealthCheck class that we can write under the io.quarkus.workshop.superheroes.hero.health sub-package:

Unresolved directive in observability-healthcheck.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/health/PingHeroResourceHealthCheck.java[tags=adocPingHero]

As you can see health check procedures are defined as implementations of the HealthCheck interface which are defined as CDI beans with the CDI qualifier @Liveness. The liveness check accessible at /health/live. HealthCheck is a functional interface whose single method call returns a HealthCheckResponse object which can be easily constructed by the fluent builder API shown in the example.

Call to action

As we have started our Quarkus application in dev mode simply repeat the request to http://localhost:8083/health/live by refreshing your browser window or by using curl http://localhost:8083/health/live. Because we defined our health check to be a liveness procedure (with @Liveness qualifier) the new health check procedure is now present in the checks array.

{
    "status": "UP",
    "checks": [
        {
            "name": "Ping Hero REST Endpoint",
            "status": "UP"
        }
    ]
}

Adding Readiness

We’ve just created a simple liveness health check procedure which states whether our application is running or not. Here, we will create a readiness health check which will be able to state whether our application is able to process requests.

We will create another health check procedure that accesses our database. If the database can be accessed, then we will always return the response indicating the application is ready.

Call to action

Create the io.quarkus.workshop.superheroes.hero.health.DatabaseConnectionHealthCheck class as follow:

Unresolved directive in observability-healthcheck.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/health/DatabaseConnectionHealthCheck.java[tags=adocDatabaseConnection]

If you now rerun the health check at http://localhost:8083/health/live the checks array will contain only the previously defined PingHeroResourceHealthCheck as it is the only check defined with the @Liveness qualifier. However, if you access http://localhost:8083/health/ready (in the browser or with curl http://localhost:8083/health/ready) you will see only the Database connection health check as it is the only health check defined with the @Readiness qualifier as the readiness health check procedure. If you access http://localhost:8083/health you will get back both checks.

{
    "status": "UP",
    "checks": [
        {
            "name": "Hero health check",
            "status": "UP",
            "data": {
                "rows": 951
            }
        },
        {
            "name": "Database connection(s) health check",
            "status": "UP"
        }
    ]
}

Health Check Tests in HeroResourceTest

Call to action

Let’s add a few extra test methods that would make sure Health Check are available in the application:

Unresolved directive in observability-healthcheck.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/test/java/io/quarkus/workshop/superheroes/hero/HeroResourceTest.java[tags=adocHealth]

Here we’ve just shown you the health check for the Hero API, but you should do the same for Fight and Villain.

Metrics

MicroProfile Metrics allows applications to gather various metrics and statistics that provide insights into what is happening inside the application.[18] The metrics can be read remotely using JSON format or the OpenMetrics format, so that they can be processed by additional tools such as Prometheus, and stored for analysis and visualisation.[19]

While you could add add metrics to all our microservices, we are focusing on the Hero microservice. You can apply the same to the other microservices.

Installing the Metrics Dependency

Call to action

To install the MicroProfile Metrics dependency, just run the following command:

$ ./mvnw quarkus:add-extension -Dextensions="metrics"

This will add the following dependency in the pom.xml file:

Unresolved directive in observability-metrics.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/pom.xml[tags=adocDependencyMetrics]

Adding Metrics to HeroResource

We want now to measure all methods of all our REST resources.

Call to action

For that, we need a few annotations to make sure that our desired metrics are calculated over time and can be exported for manual analysis or processing by additional tooling. The metrics that we will gather are these:

  • countGetRandomHero: A counter which is increased by one each time the user gets a random hero.

  • timeGetRandomHero: This is a timer, therefore a compound metric that benchmarks how much time the request take.

Below the source code of the getRandomHero() method, but make sure to add metrics on all methods:

Unresolved directive in observability-metrics.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/main/java/io/quarkus/workshop/superheroes/hero/HeroResource.java[tags=adocMetricsMethods]

Call to action

You will need to add the following import statements:

import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.annotation.Counted;
import org.eclipse.microprofile.metrics.annotation.Timed;

Metrics Tests in HeroResourceTest

Call to action

Let’s add an extra test method that would make sure Metrics are available in the application:

Unresolved directive in observability-metrics.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-hero/src/test/java/io/quarkus/workshop/superheroes/hero/HeroResourceTest.java[tags=adocMetrics]

Reviewing the Generated Metrics

To view the metrics, execute curl -H "Accept: application/json" http://localhost:8083/metrics/application. You will receive a response such as:

{
  "io.quarkus.workshop.superheroes.hero.HeroResource.countGetRandomHero": 44,
  "io.quarkus.workshop.superheroes.hero.HeroResource.timeGetRandomHero": {
    "p99": 16.227182,
    "min": 2.525987,
    "max": 16.227182,
    "mean": 3.202769680486923,
    "p50": 2.967352,
    "p999": 16.227182,
    "stddev": 1.55809730109504,
    "p95": 3.565725,
    "p98": 4.157616,
    "p75": 3.104259,
    "fiveMinRate": 0.12382047800943181,
    "fifteenMinRate": 0.04406239601227314,
    "meanRate": 0.08741866976313094,
    "count": 44,
    "oneMinRate": 0.4332024919472982
  }
}

Let’s explain the meaning of each metric:

  • countGetRandomHero: A counter which is increased by one each time the user asks for a random hero.

  • timeGetRandomHero: This is a timer, therefore a compound metric that benchmarks how much time the request takes. All durations are measured in milliseconds. It consists of these values:

    • min: The shortest duration it took to perform a request.

    • max: The longest duration.

    • mean: The mean value of the measured durations.

    • stddev: The standard deviation.

    • count: The number of observations (so it will be the same value as countGetRandomHero).

    • p50, p75, p95, p99, p999: Percentiles of the durations. For example the value in p95 means that 95 % of the measurements were faster than this duration.

    • meanRate, oneMinRate, fiveMinRate, fifteenMinRate: Mean throughput and one-, five-, and fifteen-minute exponentially-weighted moving average throughput.

If you prefer an OpenMetrics export rather than the JSON format, remove the -H "Accept: application/json" argument from your command line.

Again, in this chapter, we’ve just shown you how to add metrics for the Hero API, but you should do the same for Fight and Villain.

Loading the Microservices

Now that we have the three main microservices exposing health checks and metrics, time to have a decent user interface to monitor how the system behaves. The purpose of this workshop is to add some load to our application. You will download the load application, install it and run it.

Give me some load!

In the super-heroes/load-super-heroes directory, there is an application that is NOT a Quarkus application. It’s a simple Java app that simulates users interacting with the system so it generates some load.

Looking at Some Code

The SuperHeroesLoad class is just a main that executes the FightScenario, HeroScenario and VillainScenario in different threads. For example, if you look at the HeroScenario, you will see that it’s just a suit of HTTP calls on the Hero API:

Unresolved directive in observability-load.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/load-super-heroes/src/main/java/io/quarkus/workshop/superheroes/load/HeroScenario.java[tag=adocScenario]

Running the Load Application

Call to action

You are all set! Time to compile and start the load application using:

$ mvn compile
$ mvn exec:java

You will see the following logs:

INFO: GET - http://localhost:8082/api/fights/1 - 200
INFO: DELETE - http://localhost:8084/api/villains/440 - 204
INFO: GET - http://localhost:8083/api/heroes - 200
INFO: GET - http://localhost:8084/api/villains/hello - 200
INFO: GET - http://localhost:8082/api/fights - 200
INFO: GET - http://localhost:8083/api/heroes/581 - 200
INFO: GET - http://localhost:8084/api/villains/126 - 200
INFO: GET - http://localhost:8082/api/fights/hello - 200
INFO: DELETE - http://localhost:8083/api/heroes/491 - 204

Displaying Metrics on Prometheus

Now that we’ve added some load to our application, let’s measure it with Prometheus.[20] Prometheus is an open-source systems monitoring and alerting toolkit that integrates well with Quarkus.

Configuring Prometheus

Prometheus needs to be configured to poll data from our microservices. This is made under our infrastructure directory, in the prometheus.yml file:

Unresolved directive in observability-prometheus.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/infrastructure/monitoring/prometheus.yml[tags=adocPrometheus]

This file contains basic Prometheus configuration, plus a specific scrape_config which instructs Prometheus to look for application metrics from both Prometheus itself, and our Quarkus microservices at the /metrics endpoint.

To execute the application we now need Prometheus. Make sure the infrastructure is up and running. This means that you’ve executed docker-compose -f docker-compose.yaml up -d.

On Linux / Ubuntu

On Linux, you may need to update the localhost from the prometheus-linux.yml file with the IP associated with the docker0 interface. Find the address, update the prometheus-linux.yml and restart the infrastructure. Don’t forget to restart the microservices to re-populate the databases.

Adding Graphs to Prometheus

The Prometheus console is accessible at http://localhost:9090.

fault tolerance prometheus 1

Out of the box, you get a lot of basic JVM metrics or even metrics of Prometheus itself, which are useful. But let’s create new graphs with the metrics of our microservices.

Call to action

Tape timeGetRandomHero in the query, and select application_io_quarkus_workshop_superheroes_hero_HeroResource_timeGetRandomHero_five_min_rate_per_second in the box, and click Execute.

fault tolerance prometheus 2

This will fetch the values from our metric showing the number of checks performed:

fault tolerance prometheus 3

If you have any problem with the code, don’t understand or feel you are running, remember to ask for some help. Also, you can get the code of this entire workshop from https://github.com/quarkusio/quarkus-workshops/tree/master/quarkus-super-heroes-workshop.

Stop the load application before going further.

Event-driven and Reactive microservices


So far, we have build 3 microservices, all using HTTP to interact. However, HTTP has significant flaws, such as temporal coupling between the actors. If the service is not there or is slow, the caller is directly impacted. Also, it’s hard to guess the capacity of the service you call; maybe you should not call it right now because this service is under heavy load.

Fortunately, event-driven microservices are rising and avoid most of these issues. By using events (wrapped in messages), the different microservices enforce a looser coupling. Depending on the messaging protocol you use, it may handle durability (avoiding the temporal coupling) and back-pressure (avoiding the overload).

In this section, we are going to see how Quarkus let you build event-driven microservices. More specially, you are going to see how to:

  • send messages and process them

  • connect a Quarkus application to Apache Kafka

  • write Kafka records and read them

  • use reactive programming to compute statistics on the fly

  • how to send messages to the browser using web sockets

Quarkus uses MicroProfile Reactive Messaging to interact with Kafka, and other messaging middleware (such as AMQP).[21]

In this chapter, we are going to use events as a way for microservices to interact. You are going to extend the current system with the stats group depicted on the next figure:

diag 68553383e6bff285bde9037a7e876ca5

When the application persists a new fight, in the fight microservice, you are going to send it to a Kafka topic. These messages are read in the statistics microservice, processed, and the result is sent to a UI using web sockets.

Sending Messages to Kafka

In this section, you are going to see how you can send messages to a Kafka topic.[22]

Directory Structure

In this section we are going to extend the Fight microservice. In the following tree, we are going to edit the marked classes

diag c4a41e8b99fd1c09aefde28a080a23c0

Adding the Reactive Messaging Dependency

Call to action

To install the Kafka support, just run the following command:

$ ./mvnw quarkus:add-extension -Dextensions="kafka"

The previous command adds the following dependency:

Unresolved directive in messaging-sending-to-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/pom.xml[tag=kafkaDependency]

The extension may already have been added when the fight microservice has been created.

Then, you can restart the microservice, using mvn compile quarkus:dev.

To execute the application we now need Kafka. Make sure the infrastructure is up and running. This means that you’ve executed docker-compose -f docker-compose.yaml up -d (or the Linux variant).

Connecting Imperative and Reactive Using an Emitter

Call to action

Now edit the FightService class. First, add the following field:

Unresolved directive in messaging-sending-to-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/FightService.java[tag=adocKafkaEmitter]

You will also need to add the following imports:

import io.smallrye.reactive.messaging.annotations.Channel;
import io.smallrye.reactive.messaging.annotations.Emitter;

This field is an emitter, and lets you send events or messages (here fights) to the channel specified with the @Channel annotation. A channel is a virtual destination.

In the persistFight method, add the following line just before the return statement:

Unresolved directive in messaging-sending-to-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/java/io/quarkus/workshop/superheroes/fight/FightService.java[tag=adocKafka]

With this in place, every time the application persists a fight, it also sends the fight to the fights channel.

Connecting to Kafka

At this point, the serialized fights are sent to the fights channel. You need to connect this channel to a Kafka topic.

Call to action

For this, edit the application.properties file and add the following properties:

Unresolved directive in messaging-sending-to-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/src/main/resources/application.properties[tag=adocKafka]

These properties are structured as follows:

mp.messaging.[incoming|outgoing].channel.attribute=value

For example, mp.messaging.outgoing.fights.connector configures the connector used for the outgoing channel fights.

The mp.messaging.outgoing.fights.value.serializer configures the serializer used to write the message in Kafka. When omitted, the Kafka topic reuses the channel name (fights). Also, it connects by default to localhost:9092. You can override this using the kafka.bootstrap.servers property.

If you go back to the UI and play some fights, you would see, in the fight microservice log, something like:

Message org.eclipse.microprofile.reactive.messaging.Message$$Lambda$1021/763397690@1f6e0af3 sent successfully to Kafka topic 'fights'

Now, you have connected the fight microservice to Kafka, and you are sending new fights to the Kafka topic. Let’s see how you can read these messages in the stats microservice.

Receiving Messages from Kafka

In this section, you are going to see how you can receive messages from a Kafka topic. For this, you are going to create a new microservice, named stats. This microservice computes statistics based on the results of the fights. For example, it determines if villains win more battle than heroes, and who is the superhero or super-villain having won the most fights.

Directory Structure

In this section, we are going to develop the following structure:

diag fcfc6899eb6ad0e42978eafeef9b0071

Bootstrapping the Statistics REST Endpoint

Like for the other microservice, the easiest way to create this new Quarkus project is to use a Maven command. Under the quarkus-super-heroes-workshop/super-heroes root directory where you have all your code.

Call to action

Open a terminal and run the following command:

Unresolved directive in messaging-receiving-from-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/bootstrap.sh[tag=adocSnippet]

As you can see in the command, you can configure the extensions you want using the -Dextensions parameter. Open the pom.xml file to see the result.

Computing Statistics

Call to action

Now, create the io.quarkus.workshop.superheroes.statistics.SuperStats class with the following content. This class contains 3 methods annotated with @Incoming and @Outgoing.

The first one (toFightResults) is responsible for deserializing the JSON content received from Kafka. It gets items from the fights channel and produces items sent to the results channel. It takes a Flowable parameter and returns another Flowable. Flowable is a class provided by RX Java 2 and represents a stream of data. So, this method is called only once with a Flowable representing the incoming channel, and the returned Flowable is the outgoing channel. In the body of the method, each item from the incoming channel are mapped to FightResults.

The @Broadcast annotation indicates that several consumers (methods) consume the items produced by the method.

The computeTeamStats method follows the same pattern. It computes the ratio of victories for heroes and villains.

The computeTopWinners method uses more advanced reactive programming constructs such as groupBy, flatMap and scan:

Unresolved directive in messaging-receiving-from-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/java/io/quarkus/workshop/superheroes/statistics/SuperStats.java[tag=adocBean]

Call to action

In addition, create the io.quarkus.workshop.superheroes.statistics.FightResult, io.quarkus.workshop.superheroes.statistics.Ranking, io.quarkus.workshop.superheroes.statistics.Score and io.quarkus.workshop.superheroes.statistics.TeamStats classes with the following contents:

The FightResult class is used to deserialize the JSON object from the Kafka topic. Note the @RegisterForReflection annotation indicating to Quarkus that this class is accessed using reflection.[23] This information is required when running the application as a native executable.

Unresolved directive in messaging-receiving-from-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/java/io/quarkus/workshop/superheroes/statistics/FightResult.java[tags=adocBean;!adocSkip]

Call to action

In FightResult, add or generate the getter and setter methods for the private fields.

Then, create the Ranking class, used to compute a floating top 10, with the following content:

Unresolved directive in messaging-receiving-from-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/java/io/quarkus/workshop/superheroes/statistics/Ranking.java[tag=adocBean]

The Score class is a simple structure storing the name of a hero or villain and its actual score, i.e. the number of won battles.

Unresolved directive in messaging-receiving-from-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/java/io/quarkus/workshop/superheroes/statistics/Score.java[tags=adocBean]

The TeamStats class is an object keeping track of the number of battles won by heroes and villains.

Unresolved directive in messaging-receiving-from-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/java/io/quarkus/workshop/superheroes/statistics/TeamStats.java[]

The @RegisterForReflection annotation instructs the native compilation to allow reflection access to the class. Without, the serialization/deserialization would not work when running the native executable.

Reading Messages from Kafka

It’s now time to connect the fights channel with the Kafka topic.

Call to action

Edit the application.properties file and add the following content:

Unresolved directive in messaging-receiving-from-kafka.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/resources/application.properties[tag=adocKafka]

As for the writing side, it configures the Kafka connector. The mp.messaging.incoming.fights.auto.offset.reset=earliest property indicates that the topic is read from the earliest available record. Check the Kafka configuration to see all the available settings.

Sending Events on WebSockets

At this point, you read the fights from Kafka and computes statistics. Actually, even if you start the application, nothing will happen as nobody consumes these statistics.

In this section, we are going to consume these statistics and send them to two WebSockets. For this, we are going to add two classes and a simple presentation page:

  • TeamStatsWebSocket

  • TopWinnerWebSocket

  • index.html

Quarkus uses the JSR 356 providing an annotation-driven approach to implement WebSockets.

The TeamStats WebSocket

Call to action

Create the io.quarkus.workshop.superheroes.statistics.TeamStatsWebSocket class with the following content:

Unresolved directive in messaging-websocket.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/java/io/quarkus/workshop/superheroes/statistics/TeamStatsWebSocket.java[tag=adocWebSocket]

This component is a WebSocket as specified by the @ServerEndpoint("/stats/team") annotation. It handles the /stats/team WebSocket.

When a client (like a browser) connects to the WebSocket, it keeps track of the session. This session is released when the client disconnects.

The TeamStatsWebSocket also injects a Flowable attached to the team-stats channel. After creation, the component subscribes to this stream and broadcasts the fights to the different clients connected to the web socket.

The subscription is an essential part of the stream lifecycle. It indicates that someone is interested in the items transiting on the stream, and it triggers the emission. In this case, it triggers the connection to Kafka and starts receiving the messages from Kafka. Without it, items would not be emitted.

The TopWinner WebSocket

The io.quarkus.workshop.superheroes.statistics.TopWinnerWebSocket follows the same pattern but subscribes to the winner-stats channel.

Call to action

Creates the io.quarkus.workshop.superheroes.statistics.TopWinnerWebSocket with the following content:

Unresolved directive in messaging-websocket.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/java/io/quarkus/workshop/superheroes/statistics/TopWinnerWebSocket.java[tag=adocWebSocket]

Because the items (top 10) need to be serialized, the TopWinnerWebSocket also use JSONB to transform the object into a serialized form.

The UI

Finally, you need a UI to watch these live statistics.

Call to action

Replace the META-INF/resources/index.html file with the following content:

Unresolved directive in messaging-websocket.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/event-statistics/src/main/resources/META-INF/resources/index.html[]

Running the Application

You are all set!

Call to action

Time to start the application using:

$ mvn compile quarkus:dev

Then, open http://localhost:8085 in a new browser window. Trigger some fights, and you should see the live statistics moving.

If you have any problem with the code, don’t understand or feel you are running, remember to ask for some help. Also, you can get the code of this entire workshop from https://github.com/quarkusio/quarkus-workshops/tree/master/quarkus-super-heroes-workshop.

Unifying Imperative and Reactive Programming

So, as seen in this chapter, Quarkus is not limited to HTTP microservices, but fits perfectly in an event-driven architecture. The secret behind this is to use a single reactive engine for both imperative and reactive code:

diag cccd5e9c23129938a459745be1282daf

This unique architecture allows to mix imperative and reactive, but also use the right model for the job. To go further on this, we recommend:

Writing a Quarkus Extension


Most of the Quarkus magic happens inside extensions. The goal of an extension is to compute just enough bytecode to start the services that the application requires, and drop everything else.

So, when writing an extension, you need to distinguish the action that:

  • Can be done at build time

  • Must be done at runtime

Because of this distinction, extensions are divided into 2 parts: a build time augmentation and a runtime. The augmentation part is responsible for all the metadata processing, annotation scanning, XML parsing…​ The output of this augmentation is recorded bytecode, which, then, is executed at runtime to instantiate the relevant services.

In this chapter, you are going to implement a banner extension. Instead of having to include the bean invoked during the application startup in the application code, you are going to write an extension that does this.

The extension framework

Quarkus’s mission is to transform your entire application, including the libraries it uses, into an artifact that uses significantly fewer resources than traditional approaches. These can then be used to build native executables using GraalVM. To do this, you need to analyze and understand the full "closed world" of the application. Without the full context, the best that can be achieved is partial and limited generic support.

To build an extension, Quarkus provides a framework to:

  • read configuration from the application.properties file and map it to objects,

  • read metadata from classes without having to load them, this includes classpath and annotation scanning,

  • generate bytecode if needed (for proxies for instance),

  • pass sensible defaults to the application,

  • make the application compatible with GraalVM (resources, reflection, substitutions),

  • implement hot-reload

Structure of an extension

As stated above, an extension is divided into 2 parts, called deployment (augmentation) and runtime.

diag b7ad5c6cf999925e57ee40b4e0eae1aa

Call to action

From the directory extensions/extension-banner execute the following commands:

Unresolved directive in extension.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/extensions/extension-banner/bootstrap.sh[tag=adocSnippet]

This script creates the structure for the banner extension:

  • one parent pom.xml importing the quarkus-bom

  • a module for the runtime

  • a module for the deployment, with a dependency on the runtime artifact

The final structure of the extension developed in this section is the following:

diag 82ff6d4905e726639802c1468b006785

The banner extension

The goal of this chapter is to implement an extension that displays a textual banner when the application starts. For this, what do we need:

  1. The banner itself, in a file - so some configuration,

  2. Some code that would print the banner when the application starts; so some runtime code,

  3. Some augmentation code (build steps) that receives the configuration reads; the content of the banner file and record the runtime invocations,

  4. A way to monitor the content of the file and trigger a hot-reload in dev mode

The Runtime module

The runtime part of an extension contains only the classes and resources required at runtime. For the banner extension, it would be a single class that prints the banner.

Call to action

In the runtime module, creates the io.quarkus.workshop.superheroes.banner.runtime.BannerRecorder class with the following content:

Unresolved directive in extension.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/extensions/extension-banner/runtime/src/main/java/io/quarkus/workshop/superheroes/banner/runtime/BannerRecorder.java[]

Simple right? But how does it work? Look at the @Recorder annotation. It indicates that this class is a recorder that is used to record actions executed, later, at runtime. Indeed, these actions are replayed at runtime. We will see how this recorder is used from the deployment module.

The deployment module

This module contains build steps, i.e., methods called during the augmentation phase and computing just enough bytecode to serve the services the application requires. For the banner extension, it consists of two build steps:

  1. The first build step is going to read the banner file and use the BannerRecorder

  2. The second build step is related to the dev mode and triggers a hot-reload when the content of the banner file changes.

Call to action

In the deployment module, create the io.quarkus.workshop.superheroes.banner.deployment.BannerConfig with the following content:

Unresolved directive in extension.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/extensions/extension-banner/deployment/src/main/java/io/quarkus/workshop/superheroes/banner/deployment/BannerConfig.java[]

This class maps entries from the application.properties file to an object. It’s a convenient mechanism to avoid having to use the low-level configuration API directly. The ConfigRoot annotation indicates that this class maps properties prefixed with quarkus.banner. The class declares a single property, path, which is the quarkus.banner.path user property. Instances of this class are created by Quarkus and are used in the second part of the deployment module: the processor.

Call to action

Create the io.quarkus.workshop.superheroes.banner.deployment.BannerProcessor class with the following content:

Unresolved directive in extension.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/extensions/extension-banner/deployment/src/main/java/io/quarkus/workshop/superheroes/banner/deployment/BannerProcessor.java[]

This class is the core of the extension. It contains a set of methods annotated with @BuildStep.

The recordBanner method is responsible for recording the actions that happen at runtime. In addition to the @BuildStep annotation, it also has the @Record annotation allowing to receive a recorder object (BannerRecorder) and indicating when the recorded bytecode is replayed. Here we are replaying during the runtime initialization, i.e. equivalent to the public static void main(String…​ args) method.

The method reads the content of the banner file. This file is located using the path property from the BannerConfig object. Once the content is retrieved, it calls the recorder with the content. This invocation is going to be replayed at runtime.

Recorder at deployment time

At deployment time, proxies of recorders are injected into @BuildStep methods that have been annotated with @Record. Any invocations made on these proxies will be recorded, and bytecode will be written out to be executed at runtime to make the same sequence of invocations with the same parameters on the actual recorder objects.

At this point, the extension is functional, but don’t forget one of the pillars of Quarkus: the developer joy. The extension is also responsible for implementing hot reload logic. It is the role of the watchBannerChanges method, which indicates that the banner file must be watched, and the application restarted when this file changes.

Packaging the extension

Call to action

From the root directory of the extension, run:

mvn clean install

Using the extension

Call to action

Go back to the fight microservice, and add the following dependency to the pom.xml file:

Unresolved directive in extension.adoc - include::https://raw.githubusercontent.com/quarkusio/quarkus-workshops/master/quarkus-super-heroes-workshop/super-heroes/rest-fight/pom.xml[tag=banner]

Go to http://patorjk.com/software/taag/ to generate a banner for the fight microservice.

Call to action

Once you have the content, write it into src/main/resources/banner.txt. For instance:

          .--.            .
     _.._ |__|  .--./)  .'|
   .' .._|.--. /.''\\  <  |             .|
   | '    |  || |  | |  | |           .' |_
 __| |__  |  | \`-' /   | | .'''-.  .'     |
|__   __| |  | /("'`    | |/.'''. \'--.  .-'
   | |    |  | \ '---.  |  /    | |   |  |
   | |    |__|  /'""'.\ | |     | |   |  |
   | |         ||     ||| |     | |   |  '.'
   | |         \'. __// | '.    | '.  |   /
   |_|          `'---'  '---'   '---' `'-'

Call to action

Then, edit the src/main/resources/application.properties and add:

quarkus.banner.path=banner.txt

Call to action

Now, restart the microservice with:

mvn quarkus:dev

And the banner will be displayed. While keeping the dev mode running, edit the file, save and wait a few seconds. Once the change is detected, the application is restarted, and the banner updated.

Call to action

Let’s now check the behavior in native mode. Compile the microservice with:

mvn package -Pnative

And then start the service with:

./target/rest-fight-01-runner

Conclusion

In this section you have seen how to develop a simple extension for Quarkus. Quarkus offers a complete toolbox to implement extensions, from configuration support, tests, bytecode generation…​ The mindset to implement an extension is crucial. The distinction between build time and runtime is what makes Quarkus so efficient. To go further, check https://quarkus.io/guides/extension-authors-guide.

Containers & Cloud (optional)


This chapter explores how you can deploy Quarkus applications in containers and Cloud platforms. There are many different approaches to achieve these deployments. In this chapter, we are focusing on the creation of containers using Quarkus native executables and the deployment of our system in Kubernetes/OpenShift.

From bare metal to containers

In this section, we are going to package our microservices into containers. In particular, we are going to produce Linux 64 bits native executables and runs them in a container. The native compilation uses the OS and architecture of the host system.

And…​ Linux Containers are …​ Linux. So before being able to build a container with our native executable, we need to produce compatible native executables. If you are using a Linux 64 bits machine, you are good to go. If not, Quarkus comes with a trick to produce these executable:

$ mvn clean package -Pnative -Dnative-image.docker-build=true -DskipTests

The -Dnative-image.docker-build=true allows running the native compilation inside a container (provided by Quarkus). The result is a Linux 64 bits executable.

Building a native executable takes time, CPU, and memory. It’s even more accurate in the container. So, first, be sure that your container system has enough memory to build the executable. It requires at least 6Gb of memory, 8Gb is recommended.

Call to action

Execute the above command for all our microservices. We also copy the UI into the fight service, to simplify the process:

cd rest-hero
mvn clean package -Pnative -Dnative-image.docker-build=true -DskipTests
cd ..
cd rest-villain
mvn clean package -Pnative -Dnative-image.docker-build=true -DskipTests
cd ..
cd rest-fight
cp -R ../ui-super-heroes/dist/* src/main/resources/META-INF/resources
mvn clean package -Pnative -Dnative-image.docker-build=true -DskipTests
cd ..
cd event-statistics
mvn clean package -Pnative -Dnative-image.docker-build=true -DskipTests
cd ..

Building containers

Now that we have the native executables, we can build containers. When you create projects, Quarkus generates two Dockerfiles:

  1. Dockerfile.jvm - A Dockerfile for running the application in JVM mode

  2. Dockerfile.native - A Dockerfile for running the application in native mode

We are interested in this second file. Open one of these Dockerfile.native files:

FROM registry.access.redhat.com/ubi8/ubi-minimal
WORKDIR /work/
COPY target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

It’s a pretty straightforward Dockerfile taking a minimal base image and copying the generated native executable. It also exposes the port 8080. Wait, our microservices are not configured to run on the port 8080. We need to override this property as well as a few other such as the HTTP client endpoints, and database locations.

To build the containers, use the following scripts:

export ORG=xxxx
cd rest-hero
docker build -f src/main/docker/Dockerfile.native -t $ORG/quarkus-workshop-hero .
cd ..
cd rest-villain
docker build -f src/main/docker/Dockerfile.native -t $ORG/quarkus-workshop-villain .
cd ..
cd rest-fight
docker build -f src/main/docker/Dockerfile.native -t $ORG/quarkus-workshop-fight .
cd ..
cd event-statistics
docker build -f src/main/docker/Dockerfile.native -t $ORG/quarkus-workshop-stats .
cd ..

Replace ORG with your DockerHub / Quay.io username.

Deploying on Kubernetes

This section is going to deploy our microservices on Kubernetes. It is required to have access to a Kubernetes or OpenShift cluster.

To deploy your microservices, push the built container images to an image registry accessible by your cluster, such as Quay.io or DockerHub.

We recommend using a specific namespace to deploy your system. In the following sections, we use the quarkus-workshop namespace.

Deploying the infrastructure

The first thing to deploy is the required infrastructure:

  • 3 PostgreSQL instances

  • Kafka brokers (3 brokers with 3 Zookeeper to follow the recommended approach)

There are many ways to deploy this infrastructure. Here, we are going to use two operators:

  • PostgreSQL Operator by Dev4Ddevs.com

  • Strimzi Apache Kafka Operator by Red Hat

Call to action

With these operators installed, you can create the required infrastructure with the following custom resource definition (CRD):

apiVersion: postgresql.dev4devs.com/v1alpha1
kind: Database
metadata:
    name: heroes-database
    namespace: quarkus-workshop
spec:
    databaseCpu: 30m
    databaseCpuLimit: 60m
    databaseMemoryLimit: 512Mi
    databaseMemoryRequest: 128Mi
    databaseName: heroes-database
    databaseNameKeyEnvVar: POSTGRESQL_DATABASE
    databasePassword: superman
    databasePasswordKeyEnvVar: POSTGRESQL_PASSWORD
    databaseStorageRequest: 1Gi
    databaseUser: superman
    databaseUserKeyEnvVar: POSTGRESQL_USER
    image: centos/postgresql-96-centos7
    size: 1

Call to action

This CRD creates the database for the Hero microservice. Duplicate this CRD for the fight and villain databases.

For the Kafka broker, create the following CRD:

apiVersion: kafka.strimzi.io/v1beta1
kind: Kafka
metadata:
  name: my-kafka
  namespace: quarkus-workshop
spec:
  kafka:
    version: 2.3.0
    replicas: 3
    listeners:
      plain: {}
      tls: {}
    config:
      offsets.topic.replication.factor: 3
      transaction.state.log.replication.factor: 3
      transaction.state.log.min.isr: 2
      log.message.format.version: '2.3'
    storage:
      type: ephemeral
  zookeeper:
    replicas: 3
    storage:
      type: ephemeral
  entityOperator:
    topicOperator: {}
    userOperator: {}

This CRD creates the brokers and the Zookeeper instances.

It’s also recommended to create the topic.

Call to action

For this, create the following CRD:

apiVersion: kafka.strimzi.io/v1beta1
kind: KafkaTopic
metadata:
  name: fights
  labels:
    strimzi.io/cluster: my-kafka
  namespace: quarkus-workshop
spec:
  partitions: 1
  replicas: 3
  config:
    retention.ms: 604800000
    segment.bytes: 1073741824

Once everything is created, you should have the following resources:

$ kubectl get database
NAME                AGE
fights-database     16h
heroes-database     16h
villains-database   16h

$ kubectl get kafka
NAME       DESIRED KAFKA REPLICAS   DESIRED ZK REPLICAS
my-kafka   3

Deploying the Hero & Villain microservices

Now that the infrastructure is in place, we can deploy our microservices. Let’s start with the hero and villain microservices.

For each, we need to override the port and data source URL.

Call to action

Create a config map with the following content:

Listing 2. config-hero.yaml
apiVersion: v1
data:
    port: "8080"
    database: "jdbc:postgresql://heroes-database:5432/heroes-database"
kind: ConfigMap
metadata:
    name: hero-config

Call to action

Do the same for the villain microservice. Then, apply these resources:

$ kubectl -f config-hero.yaml
$ kubectl -f config-villain.yaml

Once the config maps are created, we can deploy the microservices.

Call to action

Create a deployment-hero.yaml file with the following content:

---
apiVersion: "v1"
kind: "List"
items:
    - apiVersion: "v1"
      kind: "Service"
      metadata:
          labels:
              app: "quarkus-workshop-hero"
              version: "01"
              group: "$ORG"
          name: "quarkus-workshop-hero"
      spec:
          ports:
              - name: "http"
                port: 8080
                targetPort: 8080
          selector:
              app: "quarkus-workshop-hero"
              version: "01"
              group: "$ORG"
          type: "ClusterIP"
    - apiVersion: "apps/v1"
      kind: "Deployment"
      metadata:
          labels:
              app: "quarkus-workshop-hero"
              version: "01"
              group: "$ORG"
          name: "quarkus-workshop-hero"
      spec:
          replicas: 1
          selector:
              matchLabels:
                  app: "quarkus-workshop-hero"
                  version: "01"
                  group: "$ORG"
          template:
              metadata:
                  labels:
                      app: "quarkus-workshop-hero"
                      version: "01"
                      group: "$ORG"
              spec:
                  containers:
                      - image: "$ORG/quarkus-workshop-hero:latest"
                        imagePullPolicy: "IfNotPresent"
                        name: "quarkus-workshop-hero"
                        ports:
                            - containerPort: 8080
                              name: "http"
                              protocol: "TCP"
                        env:
                            - name: "KUBERNETES_NAMESPACE"
                              valueFrom:
                                  fieldRef:
                                      fieldPath: "metadata.namespace"

                            - name: QUARKUS_DATASOURCE_URL
                              valueFrom:
                                  configMapKeyRef:
                                      name: hero-config
                                      key: database

                            - name: QUARKUS_HTTP_PORT
                              valueFrom:
                                  configMapKeyRef:
                                      name: hero-config
                                      key: port

This descriptor declares:

  1. A service to expose the HTTP endpoint

  2. A deployment that instantiates the application

The deployment declares one container using the container image we built earlier. It also overrides the configuration for the HTTP port and database URL.

Call to action

Don’t forget to create the equivalent files for the villain microservice.

Then, deploy the microservice with:

$ kubectl apply -f deployment-hero.yaml
$ kubectl apply -f deployment-villain.yaml

Deploying the Fight microservice

Follow the same approach for the fight microservice. Note that there are more properties to configure from the config map:

  • the location of the hero and villain microservice

  • the location of the Kafka broker.

Once everything is configured and deployed, your system is now running on Kubernetes.

Conclusion


1. RESTEasy https://resteasy.github.io
2. RestAssured http://rest-assured.io
3. Panache https://github.com/quarkusio/quarkus/tree/master/extensions/panache
4. Agroal https://agroal.github.io
5. ArC https://github.com/quarkusio/quarkus/tree/master/independent-projects/arc
6. Quarkus - Contexts and Dependency Injection https://quarkus.io/guides/cdi-reference.html
7. TestContainers https://www.testcontainers.org
8. Microprofile Config https://microprofile.io/project/eclipse/microprofile-config
9. MicroProfile OpenAPI https://github.com/eclipse/microprofile-open-api
10. Swagger UI https://swagger.io/tools/swagger-ui
11. Ahead-of-Time Compilation https://www.graalvm.org/docs/reference-manual/native-image
12. Swagger Codegen https://github.com/swagger-api/swagger-codegen
13. CORS https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
14. MicroProfile REST Client https://github.com/eclipse/microprofile-rest-client
15. Alternatives https://docs.jboss.org/weld/reference/latest/en-US/html/beanscdi.html#_alternatives
16. MicroProfile Fault Tolerance https://github.com/eclipse/microprofile-fault-tolerance
17. MicroProfile Health https://microprofile.io/project/eclipse/microprofile-health
18. MicroProfile Metrics https://microprofile.io/project/eclipse/microprofile-metrics
19. OpenMetrics https://openmetrics.io
20. Prometheus https://prometheus.io
21. MicroProfile Reactive Messaging https://github.com/eclipse/microprofile-reactive-messaging
22. Kafka Topic https://kafka.apache.org/intro#intro_topics
23. RegisterForReflection https://quarkus.io/guides/writing-native-applications-tips#alternative-with-registerforreflection